@oxyhq/core 12.9.0 → 12.10.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 +22 -12
- package/dist/cjs/crypto/keyManager.js +89 -0
- package/dist/cjs/i18n/locales/ar-SA.json +4 -0
- package/dist/cjs/i18n/locales/ca-ES.json +4 -0
- package/dist/cjs/i18n/locales/de-DE.json +4 -0
- package/dist/cjs/i18n/locales/en-US.json +69 -3
- package/dist/cjs/i18n/locales/es-ES.json +68 -2
- package/dist/cjs/i18n/locales/fr-FR.json +4 -0
- package/dist/cjs/i18n/locales/it-IT.json +4 -0
- package/dist/cjs/i18n/locales/ja-JP.json +4 -0
- package/dist/cjs/i18n/locales/ko-KR.json +4 -0
- package/dist/cjs/i18n/locales/locales/ar-SA.json +4 -0
- package/dist/cjs/i18n/locales/locales/ca-ES.json +4 -0
- package/dist/cjs/i18n/locales/locales/de-DE.json +4 -0
- package/dist/cjs/i18n/locales/locales/en-US.json +69 -3
- package/dist/cjs/i18n/locales/locales/es-ES.json +68 -2
- package/dist/cjs/i18n/locales/locales/fr-FR.json +4 -0
- package/dist/cjs/i18n/locales/locales/it-IT.json +4 -0
- package/dist/cjs/i18n/locales/locales/ja-JP.json +4 -0
- package/dist/cjs/i18n/locales/locales/ko-KR.json +4 -0
- package/dist/cjs/i18n/locales/locales/pt-PT.json +4 -0
- package/dist/cjs/i18n/locales/locales/zh-CN.json +4 -0
- package/dist/cjs/i18n/locales/pt-PT.json +4 -0
- package/dist/cjs/i18n/locales/zh-CN.json +4 -0
- package/dist/cjs/index.js +7 -4
- package/dist/cjs/mixins/OxyServices.auth.js +51 -20
- package/dist/cjs/mixins/OxyServices.connectedApps.js +6 -1
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +9 -2
- package/dist/cjs/mixins/OxyServices.identityBackup.js +11 -0
- package/dist/cjs/mixins/OxyServices.user.js +6 -0
- package/dist/cjs/session/accountDialogController.js +7 -5
- package/dist/cjs/utils/commonsApproval.js +31 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +22 -12
- package/dist/esm/crypto/keyManager.js +89 -0
- package/dist/esm/i18n/locales/ar-SA.json +4 -0
- package/dist/esm/i18n/locales/ca-ES.json +4 -0
- package/dist/esm/i18n/locales/de-DE.json +4 -0
- package/dist/esm/i18n/locales/en-US.json +69 -3
- package/dist/esm/i18n/locales/es-ES.json +68 -2
- package/dist/esm/i18n/locales/fr-FR.json +4 -0
- package/dist/esm/i18n/locales/it-IT.json +4 -0
- package/dist/esm/i18n/locales/ja-JP.json +4 -0
- package/dist/esm/i18n/locales/ko-KR.json +4 -0
- package/dist/esm/i18n/locales/locales/ar-SA.json +4 -0
- package/dist/esm/i18n/locales/locales/ca-ES.json +4 -0
- package/dist/esm/i18n/locales/locales/de-DE.json +4 -0
- package/dist/esm/i18n/locales/locales/en-US.json +69 -3
- package/dist/esm/i18n/locales/locales/es-ES.json +68 -2
- package/dist/esm/i18n/locales/locales/fr-FR.json +4 -0
- package/dist/esm/i18n/locales/locales/it-IT.json +4 -0
- package/dist/esm/i18n/locales/locales/ja-JP.json +4 -0
- package/dist/esm/i18n/locales/locales/ko-KR.json +4 -0
- package/dist/esm/i18n/locales/locales/pt-PT.json +4 -0
- package/dist/esm/i18n/locales/locales/zh-CN.json +4 -0
- package/dist/esm/i18n/locales/pt-PT.json +4 -0
- package/dist/esm/i18n/locales/zh-CN.json +4 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/mixins/OxyServices.auth.js +48 -19
- package/dist/esm/mixins/OxyServices.connectedApps.js +6 -1
- package/dist/esm/mixins/OxyServices.deviceBoot.js +9 -2
- package/dist/esm/mixins/OxyServices.identityBackup.js +11 -0
- package/dist/esm/mixins/OxyServices.user.js +6 -0
- package/dist/esm/session/accountDialogController.js +7 -5
- package/dist/esm/utils/commonsApproval.js +27 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +14 -0
- package/dist/types/crypto/keyManager.d.ts +33 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +4 -4
- package/dist/types/session/accountDialogController.d.ts +7 -2
- package/dist/types/utils/commonsApproval.d.ts +13 -0
- package/package.json +3 -3
- package/src/HttpService.ts +37 -14
- package/src/__tests__/httpServiceAuthSelfAwait.test.ts +108 -0
- package/src/__tests__/httpServiceQueueDeadlock.test.ts +128 -0
- package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +138 -0
- package/src/crypto/keyManager.ts +99 -0
- package/src/i18n/locales/ar-SA.json +4 -0
- package/src/i18n/locales/ca-ES.json +4 -0
- package/src/i18n/locales/de-DE.json +4 -0
- package/src/i18n/locales/en-US.json +69 -3
- package/src/i18n/locales/es-ES.json +68 -2
- package/src/i18n/locales/fr-FR.json +4 -0
- package/src/i18n/locales/it-IT.json +4 -0
- package/src/i18n/locales/ja-JP.json +4 -0
- package/src/i18n/locales/ko-KR.json +4 -0
- package/src/i18n/locales/pt-PT.json +4 -0
- package/src/i18n/locales/zh-CN.json +4 -0
- package/src/index.ts +7 -1
- package/src/mixins/OxyServices.auth.ts +48 -24
- package/src/mixins/OxyServices.connectedApps.ts +6 -1
- package/src/mixins/OxyServices.deviceBoot.ts +7 -2
- package/src/mixins/OxyServices.identityBackup.ts +13 -0
- package/src/mixins/OxyServices.user.ts +6 -0
- package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +4 -2
- package/src/mixins/__tests__/commonsSignIn.test.ts +4 -4
- package/src/mixins/__tests__/identityBackup.test.ts +57 -0
- package/src/mixins/__tests__/preSessionSkipAuth.test.ts +152 -0
- package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +6 -0
- package/src/mixins/__tests__/webauthnAuth.test.ts +6 -6
- package/src/session/__tests__/accountDialogController.test.ts +3 -3
- package/src/session/accountDialogController.ts +7 -6
- package/src/utils/__tests__/commonsApproval.test.ts +60 -0
- package/src/utils/commonsApproval.ts +39 -0
|
@@ -290,6 +290,8 @@
|
|
|
290
290
|
"saving": "Guardando…",
|
|
291
291
|
"helper": "El círculo recortado es lo que aparecerá en tu perfil. Pellizca para acercar, arrastra para colocar.",
|
|
292
292
|
"zoom": "{{value}}×",
|
|
293
|
+
"zoomIn": "Acercar",
|
|
294
|
+
"zoomOut": "Alejar",
|
|
293
295
|
"a11yImage": "Vista previa del recorte. Pellizca para acercar y arrastra para reposicionar la imagen.",
|
|
294
296
|
"a11yReset": "Restablecer el recorte a la posición predeterminada",
|
|
295
297
|
"a11yResetAnnouncement": "Recorte restablecido"
|
|
@@ -310,6 +312,47 @@
|
|
|
310
312
|
},
|
|
311
313
|
"changeAvatar": "Cambiar avatar"
|
|
312
314
|
},
|
|
315
|
+
"changeAvatar": {
|
|
316
|
+
"title": "Cambiar foto de perfil",
|
|
317
|
+
"sources": {
|
|
318
|
+
"upload": {
|
|
319
|
+
"title": "Subir desde el dispositivo",
|
|
320
|
+
"description": "Elige una foto de tu galería"
|
|
321
|
+
},
|
|
322
|
+
"camera": {
|
|
323
|
+
"title": "Hacer una foto",
|
|
324
|
+
"description": "Usa tu cámara"
|
|
325
|
+
},
|
|
326
|
+
"files": {
|
|
327
|
+
"title": "Mis archivos de Oxy",
|
|
328
|
+
"description": "Elige una imagen que ya has subido"
|
|
329
|
+
},
|
|
330
|
+
"remove": {
|
|
331
|
+
"title": "Eliminar la foto actual",
|
|
332
|
+
"description": "Vuelve a tus iniciales"
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
"remove": {
|
|
336
|
+
"confirmTitle": "¿Eliminar la foto de perfil?",
|
|
337
|
+
"confirmMessage": "Tu perfil mostrará tus iniciales. Puedes añadir una foto nueva cuando quieras.",
|
|
338
|
+
"confirmLabel": "Eliminar"
|
|
339
|
+
},
|
|
340
|
+
"permission": {
|
|
341
|
+
"libraryDenied": "Oxy necesita acceso a tus fotos para subir una imagen.",
|
|
342
|
+
"cameraDenied": "Oxy necesita acceso a tu cámara para hacer una foto.",
|
|
343
|
+
"openSettings": "Abrir ajustes"
|
|
344
|
+
},
|
|
345
|
+
"errors": {
|
|
346
|
+
"pickerUnavailable": "La selección de fotos no está disponible en este dispositivo.",
|
|
347
|
+
"cameraUnavailable": "La cámara no está disponible en este dispositivo.",
|
|
348
|
+
"pickFailed": "No se pudo abrir el selector de fotos.",
|
|
349
|
+
"cameraFailed": "No se pudo abrir la cámara.",
|
|
350
|
+
"loadImageFailed": "No se pudo cargar la imagen seleccionada."
|
|
351
|
+
},
|
|
352
|
+
"a11y": {
|
|
353
|
+
"currentPhoto": "Tu foto de perfil actual"
|
|
354
|
+
}
|
|
355
|
+
},
|
|
313
356
|
"common": {
|
|
314
357
|
"actions": {
|
|
315
358
|
"back": "Atrás",
|
|
@@ -619,7 +662,8 @@
|
|
|
619
662
|
"getCommons": "Descargar Commons",
|
|
620
663
|
"commonsNotInstalled": "¿No tienes Commons? Descarga la app para iniciar sesión con tu Oxy ID.",
|
|
621
664
|
"showQrAnyway": "Tengo Commons en otro dispositivo",
|
|
622
|
-
"switchWhileSignedInAs": "Cambiar de cuenta, con la sesión iniciada como {{name}}"
|
|
665
|
+
"switchWhileSignedInAs": "Cambiar de cuenta, con la sesión iniciada como {{name}}",
|
|
666
|
+
"manageOnDevice": "Gestionar cuentas en este dispositivo"
|
|
623
667
|
},
|
|
624
668
|
"feedback": {
|
|
625
669
|
"type": {
|
|
@@ -1442,6 +1486,11 @@
|
|
|
1442
1486
|
"uploadPhotos": "Subir fotos",
|
|
1443
1487
|
"uploadFiles": "Subir archivos",
|
|
1444
1488
|
"clearSearch": "Limpiar búsqueda",
|
|
1489
|
+
"retry": "Reintentar",
|
|
1490
|
+
"loadError": {
|
|
1491
|
+
"title": "No se pudieron cargar tus archivos",
|
|
1492
|
+
"description": "Algo salió mal. Comprueba tu conexión e inténtalo de nuevo."
|
|
1493
|
+
},
|
|
1445
1494
|
"choosePhoto": "Elegir foto",
|
|
1446
1495
|
"done": "Listo",
|
|
1447
1496
|
"doneWithCount": "Listo ({{count}})",
|
|
@@ -1615,6 +1664,8 @@
|
|
|
1615
1664
|
"accountMenu": {
|
|
1616
1665
|
"label": "Menú de cuenta",
|
|
1617
1666
|
"manage": "Gestiona tu cuenta de Oxy",
|
|
1667
|
+
"greeting": "¡Hola, {{name}}!",
|
|
1668
|
+
"switchAccount": "Cambiar de cuenta",
|
|
1618
1669
|
"viewProfile": "Ver perfil",
|
|
1619
1670
|
"addAnother": "Añadir otra cuenta",
|
|
1620
1671
|
"signOutAll": "Cerrar sesión en todas las cuentas",
|
|
@@ -1622,7 +1673,22 @@
|
|
|
1622
1673
|
"openHint": "Abre el menú de cuenta",
|
|
1623
1674
|
"openWithUser": "Menú de cuenta de {{name}}",
|
|
1624
1675
|
"switching": "Cambiando de cuenta…",
|
|
1625
|
-
"signOutAccount": "Cerrar sesión de {{name}}"
|
|
1676
|
+
"signOutAccount": "Cerrar sesión de {{name}}",
|
|
1677
|
+
"storage": {
|
|
1678
|
+
"title": "Almacenamiento de Oxy",
|
|
1679
|
+
"usage": "{{used}} de {{total}} en uso",
|
|
1680
|
+
"used": "Usado",
|
|
1681
|
+
"free": "Libre",
|
|
1682
|
+
"unavailable": "Detalles de uso no disponibles",
|
|
1683
|
+
"upgrade": "Mejorar plan",
|
|
1684
|
+
"manage": "Gestionar almacenamiento"
|
|
1685
|
+
},
|
|
1686
|
+
"data": "Tus datos en Oxy",
|
|
1687
|
+
"settings": "Ajustes de Oxy",
|
|
1688
|
+
"help": "Ayuda y comentarios",
|
|
1689
|
+
"signOut": "Cerrar sesión",
|
|
1690
|
+
"privacy": "Política de privacidad",
|
|
1691
|
+
"terms": "Términos del servicio"
|
|
1626
1692
|
},
|
|
1627
1693
|
"accounts": {
|
|
1628
1694
|
"activeAccount": {
|
|
@@ -112,6 +112,10 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
"accountSwitcher": {
|
|
115
|
+
"sections": {
|
|
116
|
+
"yourAccounts": "Vos comptes"
|
|
117
|
+
},
|
|
118
|
+
"loading": "Chargement des comptes...",
|
|
115
119
|
"qrHeadline": "Connectez-vous avec votre identité Oxy",
|
|
116
120
|
"signInWithOxy": "Se connecter avec Oxy",
|
|
117
121
|
"useIdentityOnDevice": "Pas de Commons ? Utilisez une clé d'accès sur cet appareil",
|
|
@@ -112,6 +112,10 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
"accountSwitcher": {
|
|
115
|
+
"sections": {
|
|
116
|
+
"yourAccounts": "I tuoi account"
|
|
117
|
+
},
|
|
118
|
+
"loading": "Caricamento account...",
|
|
115
119
|
"qrHeadline": "Accedi con la tua identità Oxy",
|
|
116
120
|
"signInWithOxy": "Accedi con Oxy",
|
|
117
121
|
"useIdentityOnDevice": "Non hai Commons? Usa una passkey su questo dispositivo",
|
|
@@ -112,6 +112,10 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
"accountSwitcher": {
|
|
115
|
+
"sections": {
|
|
116
|
+
"yourAccounts": "あなたのアカウント"
|
|
117
|
+
},
|
|
118
|
+
"loading": "アカウントを読み込み中...",
|
|
115
119
|
"qrHeadline": "Oxy IDでサインイン",
|
|
116
120
|
"signInWithOxy": "Oxyでサインイン",
|
|
117
121
|
"useIdentityOnDevice": "Commonsがありませんか?このデバイスのパスキーを使用",
|
|
@@ -112,6 +112,10 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
"accountSwitcher": {
|
|
115
|
+
"sections": {
|
|
116
|
+
"yourAccounts": "내 계정"
|
|
117
|
+
},
|
|
118
|
+
"loading": "계정을 불러오는 중...",
|
|
115
119
|
"qrHeadline": "Oxy ID로 로그인",
|
|
116
120
|
"signInWithOxy": "Oxy로 로그인",
|
|
117
121
|
"useIdentityOnDevice": "Commons가 없으신가요? 이 기기의 패스키를 사용하세요",
|
|
@@ -112,6 +112,10 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
"accountSwitcher": {
|
|
115
|
+
"sections": {
|
|
116
|
+
"yourAccounts": "As suas contas"
|
|
117
|
+
},
|
|
118
|
+
"loading": "A carregar contas...",
|
|
115
119
|
"qrHeadline": "Inicie sessão com a sua identidade Oxy",
|
|
116
120
|
"signInWithOxy": "Iniciar sessão com Oxy",
|
|
117
121
|
"useIdentityOnDevice": "Não tem o Commons? Use uma chave de acesso neste dispositivo",
|
|
@@ -112,6 +112,10 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
"accountSwitcher": {
|
|
115
|
+
"sections": {
|
|
116
|
+
"yourAccounts": "As suas contas"
|
|
117
|
+
},
|
|
118
|
+
"loading": "A carregar contas...",
|
|
115
119
|
"qrHeadline": "Inicie sessão com a sua identidade Oxy",
|
|
116
120
|
"signInWithOxy": "Iniciar sessão com Oxy",
|
|
117
121
|
"useIdentityOnDevice": "Não tem o Commons? Use uma chave de acesso neste dispositivo",
|
package/dist/esm/index.js
CHANGED
|
@@ -26,7 +26,8 @@ export { OXY_CLOUD_URL, oxyClient } from './OxyServices.js';
|
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
// Authentication
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
|
-
export { ServiceCredentialMismatchError } from './mixins/OxyServices.auth.js';
|
|
29
|
+
export { ServiceCredentialMismatchError, } from './mixins/OxyServices.auth.js';
|
|
30
|
+
export { getCommonsApprovalBlockingReason, parseCommonsApprovalExpiresAt, } from './utils/commonsApproval.js';
|
|
30
31
|
export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData.js';
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// User identity and handles
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loginResultSchema, safeParseContract } from '@oxyhq/contracts';
|
|
2
|
+
export { getCommonsApprovalBlockingReason, parseCommonsApprovalExpiresAt, } from '../utils/commonsApproval.js';
|
|
2
3
|
import { OxyAuthenticationError } from '../OxyServices.errors.js';
|
|
3
4
|
import { KeyManager } from '../crypto/keyManager.js';
|
|
4
5
|
import { SignatureService } from '../crypto/signatureService.js';
|
|
@@ -181,7 +182,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
181
182
|
* @internal
|
|
182
183
|
*/
|
|
183
184
|
async _doFetchServiceToken(key, secret, cacheKey, secretBuf) {
|
|
184
|
-
const response = await this.makeRequest('POST', '/auth/service-token', { apiKey: key, apiSecret: secret }, { cache: false, retry: false });
|
|
185
|
+
const response = await this.makeRequest('POST', '/auth/service-token', { apiKey: key, apiSecret: secret }, { cache: false, retry: false, skipAuth: true });
|
|
185
186
|
const expiresAt = Date.now() + response.expiresIn * 1000;
|
|
186
187
|
// Update the entry in-place so any caller that already grabbed a reference
|
|
187
188
|
// (via `_serviceTokenCache.get(...)`) sees the fresh state.
|
|
@@ -278,7 +279,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
278
279
|
publicKey,
|
|
279
280
|
signature,
|
|
280
281
|
timestamp,
|
|
281
|
-
}, { cache: false });
|
|
282
|
+
}, { cache: false, skipAuth: true });
|
|
282
283
|
if (!res || (typeof res === 'object' && Object.keys(res).length === 0)) {
|
|
283
284
|
throw new OxyAuthenticationError('Registration failed', 'REGISTER_FAILED', 400);
|
|
284
285
|
}
|
|
@@ -302,7 +303,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
302
303
|
try {
|
|
303
304
|
return await this.makeRequest('POST', '/auth/challenge', {
|
|
304
305
|
publicKey,
|
|
305
|
-
}, { cache: false, ...requestOptions });
|
|
306
|
+
}, { cache: false, skipAuth: true, ...requestOptions });
|
|
306
307
|
}
|
|
307
308
|
catch (error) {
|
|
308
309
|
throw this.handleError(error);
|
|
@@ -331,7 +332,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
331
332
|
timestamp,
|
|
332
333
|
deviceName,
|
|
333
334
|
deviceFingerprint,
|
|
334
|
-
}, { cache: false, ...requestOptions });
|
|
335
|
+
}, { cache: false, skipAuth: true, ...requestOptions });
|
|
335
336
|
// Plant the freshly-minted tokens, mirroring `claimSessionByToken`.
|
|
336
337
|
// `/auth/verify` returns the first access token (and refresh token) in
|
|
337
338
|
// its body, so installing it here means callers get an authenticated
|
|
@@ -354,7 +355,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
354
355
|
*/
|
|
355
356
|
async checkPublicKeyRegistered(publicKey) {
|
|
356
357
|
try {
|
|
357
|
-
return await this.makeRequest('GET', `/auth/check-publickey/${encodeURIComponent(publicKey)}`, undefined, { cache: false });
|
|
358
|
+
return await this.makeRequest('GET', `/auth/check-publickey/${encodeURIComponent(publicKey)}`, undefined, { cache: false, skipAuth: true });
|
|
358
359
|
}
|
|
359
360
|
catch (error) {
|
|
360
361
|
throw this.handleError(error);
|
|
@@ -365,7 +366,12 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
365
366
|
*/
|
|
366
367
|
async getUserByPublicKey(publicKey) {
|
|
367
368
|
try {
|
|
368
|
-
const user = await this.makeRequest('GET', `/auth/user/${encodeURIComponent(publicKey)}`, undefined, {
|
|
369
|
+
const user = await this.makeRequest('GET', `/auth/user/${encodeURIComponent(publicKey)}`, undefined, {
|
|
370
|
+
cache: true,
|
|
371
|
+
cacheTTL: 2 * 60 * 1000,
|
|
372
|
+
// Public lookup by public key (pre-session) — skip the bearer preflight.
|
|
373
|
+
skipAuth: true,
|
|
374
|
+
});
|
|
369
375
|
return normalizeUserIdentity(user);
|
|
370
376
|
}
|
|
371
377
|
catch (error) {
|
|
@@ -436,7 +442,9 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
436
442
|
const res = await this.makeRequest('POST', '/auth/session/claim', {
|
|
437
443
|
sessionToken,
|
|
438
444
|
...(options.deviceFingerprint ? { deviceFingerprint: options.deviceFingerprint } : {}),
|
|
439
|
-
},
|
|
445
|
+
},
|
|
446
|
+
// Body-authenticated device-flow claim (no bearer) — skip the preflight.
|
|
447
|
+
{ cache: false, retry: false, skipAuth: true });
|
|
440
448
|
this.setTokens(res.accessToken);
|
|
441
449
|
return res;
|
|
442
450
|
}
|
|
@@ -520,7 +528,11 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
520
528
|
// QR. Reuses the platform-safe random generator.
|
|
521
529
|
const sessionToken = await SignatureService.generateChallenge();
|
|
522
530
|
const expiresAt = Date.now() + COMMONS_SIGN_IN_EXPIRY_MS;
|
|
523
|
-
const res = await this.makeRequest('POST', '/auth/session/create', { sessionToken, expiresAt, clientId: params.clientId },
|
|
531
|
+
const res = await this.makeRequest('POST', '/auth/session/create', { sessionToken, expiresAt, clientId: params.clientId },
|
|
532
|
+
// Public/pre-session (no bearer): skip the preflight so a stale
|
|
533
|
+
// near-expiry token cannot re-enter refreshAccessToken while the
|
|
534
|
+
// refresh handler is already in flight (self-await hang).
|
|
535
|
+
{ cache: false, skipAuth: true });
|
|
524
536
|
return {
|
|
525
537
|
sessionToken,
|
|
526
538
|
authorizeCode: res.authorizeCode,
|
|
@@ -544,7 +556,11 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
544
556
|
*/
|
|
545
557
|
async pollCommonsSignIn(sessionToken) {
|
|
546
558
|
try {
|
|
547
|
-
return await this.makeRequest('GET', `/auth/session/status/${encodeURIComponent(sessionToken)}`, undefined,
|
|
559
|
+
return await this.makeRequest('GET', `/auth/session/status/${encodeURIComponent(sessionToken)}`, undefined,
|
|
560
|
+
// Public/pre-session (no bearer): a preflight here is wrong per se and,
|
|
561
|
+
// if ever reached while a refresh is pending, would re-enter
|
|
562
|
+
// refreshAccessToken and await the very promise it runs inside.
|
|
563
|
+
{ cache: false, retry: false, skipAuth: true });
|
|
548
564
|
}
|
|
549
565
|
catch (error) {
|
|
550
566
|
throw this.handleError(error);
|
|
@@ -562,7 +578,10 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
562
578
|
*/
|
|
563
579
|
async getCommonsApprovalInfo(authorizeCode) {
|
|
564
580
|
try {
|
|
565
|
-
const raw = await this.makeRequest('GET', `/auth/session/approve-info/${encodeURIComponent(authorizeCode)}`, undefined,
|
|
581
|
+
const raw = await this.makeRequest('GET', `/auth/session/approve-info/${encodeURIComponent(authorizeCode)}`, undefined,
|
|
582
|
+
// Public (no auth required) — skip the bearer preflight (avoids the
|
|
583
|
+
// pre-session self-await class).
|
|
584
|
+
{ cache: false, skipAuth: true });
|
|
566
585
|
return {
|
|
567
586
|
application: raw.application,
|
|
568
587
|
scopes: raw.scopes,
|
|
@@ -609,7 +628,9 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
609
628
|
timestamp: signed.timestamp,
|
|
610
629
|
...(params.deviceName ? { deviceName: params.deviceName } : {}),
|
|
611
630
|
...(params.deviceFingerprint ? { deviceFingerprint: params.deviceFingerprint } : {}),
|
|
612
|
-
},
|
|
631
|
+
},
|
|
632
|
+
// Key-signed, cookieless (no bearer) — skip the preflight.
|
|
633
|
+
{ cache: false, skipAuth: true });
|
|
613
634
|
}
|
|
614
635
|
catch (error) {
|
|
615
636
|
throw this.handleError(error);
|
|
@@ -623,7 +644,9 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
623
644
|
*/
|
|
624
645
|
async denyCommonsSignIn(authorizeCode) {
|
|
625
646
|
try {
|
|
626
|
-
return await this.makeRequest('POST', `/auth/session/deny/${encodeURIComponent(authorizeCode)}`, undefined,
|
|
647
|
+
return await this.makeRequest('POST', `/auth/session/deny/${encodeURIComponent(authorizeCode)}`, undefined,
|
|
648
|
+
// Public (no auth required) — skip the bearer preflight.
|
|
649
|
+
{ cache: false, skipAuth: true });
|
|
627
650
|
}
|
|
628
651
|
catch (error) {
|
|
629
652
|
throw this.handleError(error);
|
|
@@ -734,7 +757,8 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
734
757
|
*/
|
|
735
758
|
async checkUsernameAvailability(username) {
|
|
736
759
|
try {
|
|
737
|
-
|
|
760
|
+
// Public availability lookup (pre-session) — skip the bearer preflight.
|
|
761
|
+
return await this.makeRequest('GET', `/auth/check-username/${username}`, undefined, { cache: false, skipAuth: true });
|
|
738
762
|
}
|
|
739
763
|
catch (error) {
|
|
740
764
|
throw this.handleError(error);
|
|
@@ -745,7 +769,8 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
745
769
|
*/
|
|
746
770
|
async checkEmailAvailability(email) {
|
|
747
771
|
try {
|
|
748
|
-
|
|
772
|
+
// Public availability lookup (pre-session) — skip the bearer preflight.
|
|
773
|
+
return await this.makeRequest('GET', `/auth/check-email/${email}`, undefined, { cache: false, skipAuth: true });
|
|
749
774
|
}
|
|
750
775
|
catch (error) {
|
|
751
776
|
throw this.handleError(error);
|
|
@@ -764,7 +789,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
764
789
|
*/
|
|
765
790
|
async webauthnRegisterOptions(username) {
|
|
766
791
|
try {
|
|
767
|
-
return await this.makeRequest('POST', '/auth/webauthn/register/options', { ...(username !== undefined ? { username } : {}) }, { cache: false });
|
|
792
|
+
return await this.makeRequest('POST', '/auth/webauthn/register/options', { ...(username !== undefined ? { username } : {}) }, { cache: false, ...(username !== undefined ? { skipAuth: true } : {}) });
|
|
768
793
|
}
|
|
769
794
|
catch (error) {
|
|
770
795
|
throw this.handleError(error);
|
|
@@ -785,7 +810,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
785
810
|
*/
|
|
786
811
|
async webauthnRegisterVerify(response, envelope = {}) {
|
|
787
812
|
try {
|
|
788
|
-
const res = await this.makeRequest('POST', '/auth/webauthn/register/verify', { response, ...envelope }, { cache: false });
|
|
813
|
+
const res = await this.makeRequest('POST', '/auth/webauthn/register/verify', { response, ...envelope }, { cache: false, ...(envelope.username !== undefined ? { skipAuth: true } : {}) });
|
|
789
814
|
if (res && typeof res === 'object') {
|
|
790
815
|
const record = res;
|
|
791
816
|
// Signup branch: mints a session (LoginSessionResult, carries
|
|
@@ -823,7 +848,9 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
823
848
|
*/
|
|
824
849
|
async webauthnLoginOptions(username) {
|
|
825
850
|
try {
|
|
826
|
-
return await this.makeRequest('POST', '/auth/webauthn/login/options', { ...(username !== undefined ? { username } : {}) },
|
|
851
|
+
return await this.makeRequest('POST', '/auth/webauthn/login/options', { ...(username !== undefined ? { username } : {}) },
|
|
852
|
+
// Pre-session login ceremony — skip the bearer preflight.
|
|
853
|
+
{ cache: false, skipAuth: true });
|
|
827
854
|
}
|
|
828
855
|
catch (error) {
|
|
829
856
|
throw this.handleError(error);
|
|
@@ -839,7 +866,9 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
839
866
|
*/
|
|
840
867
|
async webauthnLoginVerify(response, envelope = {}) {
|
|
841
868
|
try {
|
|
842
|
-
const res = await this.makeRequest('POST', '/auth/webauthn/login/verify', { response, ...envelope },
|
|
869
|
+
const res = await this.makeRequest('POST', '/auth/webauthn/login/verify', { response, ...envelope },
|
|
870
|
+
// Pre-session login ceremony — skip the bearer preflight.
|
|
871
|
+
{ cache: false, skipAuth: true });
|
|
843
872
|
const parsed = safeParseContract(loginResultSchema, res);
|
|
844
873
|
if (!parsed) {
|
|
845
874
|
throw new Error('auth/webauthn/login/verify returned an unexpected response shape');
|
|
@@ -866,7 +895,7 @@ export function OxyServicesAuthMixin(Base) {
|
|
|
866
895
|
clientId: params.clientId,
|
|
867
896
|
redirectUri: params.redirectUri,
|
|
868
897
|
codeVerifier: params.codeVerifier,
|
|
869
|
-
}, { cache: false });
|
|
898
|
+
}, { cache: false, skipAuth: true });
|
|
870
899
|
const payload = res.data ??
|
|
871
900
|
res;
|
|
872
901
|
if (!payload || typeof payload !== 'object') {
|
|
@@ -16,7 +16,12 @@ export function OxyServicesConnectedAppsMixin(Base) {
|
|
|
16
16
|
*/
|
|
17
17
|
async getPublicApplication(clientId) {
|
|
18
18
|
try {
|
|
19
|
-
const res = await this.makeRequest('GET', `/auth/oauth/client/${encodeURIComponent(clientId)}`, undefined, {
|
|
19
|
+
const res = await this.makeRequest('GET', `/auth/oauth/client/${encodeURIComponent(clientId)}`, undefined, {
|
|
20
|
+
cache: true,
|
|
21
|
+
cacheTTL: CACHE_TIMES.MEDIUM,
|
|
22
|
+
// Public client metadata (pre-session consent UI) — skip the bearer preflight.
|
|
23
|
+
skipAuth: true,
|
|
24
|
+
});
|
|
20
25
|
return res.application;
|
|
21
26
|
}
|
|
22
27
|
catch (error) {
|
|
@@ -41,7 +41,11 @@ export function OxyServicesDeviceBootMixin(Base) {
|
|
|
41
41
|
*/
|
|
42
42
|
async mintFromDeviceSecret(deviceId, deviceSecret) {
|
|
43
43
|
try {
|
|
44
|
-
const res = await this.makeRequest('POST', '/session/device/token', { deviceId, deviceSecret },
|
|
44
|
+
const res = await this.makeRequest('POST', '/session/device/token', { deviceId, deviceSecret },
|
|
45
|
+
// `bypassQueue`: this mint is the control-plane call the auth lane
|
|
46
|
+
// depends on — it must run even when every RequestQueue slot is parked
|
|
47
|
+
// awaiting it, or the whole client deadlocks. See RequestOptions.bypassQueue.
|
|
48
|
+
{ cache: false, skipAuth: true, retry: false, bypassQueue: true });
|
|
45
49
|
const parsed = safeParseContract(deviceTokenMintResponseSchema, res);
|
|
46
50
|
if (!parsed) {
|
|
47
51
|
throw new Error('session/device/token returned an unexpected response shape');
|
|
@@ -69,7 +73,10 @@ export function OxyServicesDeviceBootMixin(Base) {
|
|
|
69
73
|
/** Redeem a hub sync ticket for a fresh device secret (public). */
|
|
70
74
|
async redeemHubTicket(ticket, returnOrigin) {
|
|
71
75
|
try {
|
|
72
|
-
const res = await this.makeRequest('POST', '/session/device/redeem-ticket', { ticket, returnOrigin },
|
|
76
|
+
const res = await this.makeRequest('POST', '/session/device/redeem-ticket', { ticket, returnOrigin },
|
|
77
|
+
// Public device-hub sync mint (bearer-less). Same control-plane class as
|
|
78
|
+
// the device-secret mint — bypassQueue so it never waits for a slot.
|
|
79
|
+
{ cache: false, skipAuth: true, bypassQueue: true });
|
|
73
80
|
const parsed = safeParseContract(deviceHubTicketRedeemResponseSchema, res);
|
|
74
81
|
if (!parsed) {
|
|
75
82
|
throw new Error('session/device/redeem-ticket returned an unexpected response shape');
|
|
@@ -138,6 +138,17 @@ export function OxyServicesIdentityBackupMixin(Base) {
|
|
|
138
138
|
const aad = buildBackupAad(envelope.version, envelope.publicKeyHint);
|
|
139
139
|
const plaintext = decryptAead(backupKey, fromHex(envelope.nonce), fromHex(envelope.ciphertext), aad);
|
|
140
140
|
const payload = JSON.parse(new TextDecoder().decode(plaintext));
|
|
141
|
+
if (!payload.privateKey || !payload.publicKey) {
|
|
142
|
+
throw new Error('Backup payload is missing key material');
|
|
143
|
+
}
|
|
144
|
+
const derivedFromPhrase = await RecoveryPhraseService.derivePublicKeyFromPhrase(phrase);
|
|
145
|
+
const derivedFromPrivate = KeyManager.derivePublicKey(payload.privateKey);
|
|
146
|
+
const phrasePk = derivedFromPhrase.toLowerCase();
|
|
147
|
+
const payloadPk = payload.publicKey.toLowerCase();
|
|
148
|
+
const privatePk = derivedFromPrivate.toLowerCase();
|
|
149
|
+
if (phrasePk !== payloadPk || privatePk !== payloadPk) {
|
|
150
|
+
throw new Error('Backup payload does not match the recovery phrase');
|
|
151
|
+
}
|
|
141
152
|
// Persist the recovered key. Native-only; refuses to clobber a different
|
|
142
153
|
// identity unless overwrite — the IdentityAlreadyExistsError propagates.
|
|
143
154
|
return await KeyManager.importKeyPair(payload.privateKey, {
|
|
@@ -56,6 +56,8 @@ export function OxyServicesUserMixin(Base) {
|
|
|
56
56
|
return await this.makeRequest('GET', `/auth/lookup/${encodeURIComponent(username)}`, undefined, {
|
|
57
57
|
cache: true,
|
|
58
58
|
cacheTTL: 60 * 1000, // 1 minute cache
|
|
59
|
+
// Public login-flow lookup (pre-session) — skip the bearer preflight.
|
|
60
|
+
skipAuth: true,
|
|
59
61
|
});
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
@@ -420,6 +422,10 @@ export function OxyServicesUserMixin(Base) {
|
|
|
420
422
|
const result = await this.makeRequest('PATCH', `/privacy/${id}/privacy`, settings, {
|
|
421
423
|
cache: false,
|
|
422
424
|
});
|
|
425
|
+
this.clearCacheByPrefix('GET:/session/user/');
|
|
426
|
+
this.clearCacheByPrefix('GET:/users/me');
|
|
427
|
+
this.clearCacheByPrefix('GET:/profiles/username/');
|
|
428
|
+
this.clearCacheEntry(`GET:/users/${id}`);
|
|
423
429
|
this.clearCacheEntry(`GET:/privacy/${id}/privacy`);
|
|
424
430
|
return result;
|
|
425
431
|
}
|
|
@@ -35,6 +35,13 @@
|
|
|
35
35
|
* password, social login, and 2FA were removed ecosystem-wide. Account
|
|
36
36
|
* creation (`signup` view) is the same two identity backends: a passkey
|
|
37
37
|
* ceremony on web, or a Commons-created identity.
|
|
38
|
+
*
|
|
39
|
+
* The controller owns NO surface PRESENTATION: whether the account dialog is
|
|
40
|
+
* mounted, visible, or dismissed is the job of the shared surface stack
|
|
41
|
+
* (`@oxyhq/services` presents the `AccountDialog` surface into `@oxyhq/bloom`'s
|
|
42
|
+
* stack). This is a headless state machine only — it exposes `setView` /
|
|
43
|
+
* `add` / `startSignup` (the view axis) and `cancelSignIn` (device-flow
|
|
44
|
+
* teardown), never an `open` / `close` / `visible`.
|
|
38
45
|
*/
|
|
39
46
|
import { logger } from '../logger/index.js';
|
|
40
47
|
import { extractErrorStatus } from '../utils/errorUtils.js';
|
|
@@ -248,11 +255,6 @@ export class AccountDialogController {
|
|
|
248
255
|
this.view = view;
|
|
249
256
|
this.emit();
|
|
250
257
|
}
|
|
251
|
-
/** Return to the account list and cancel any in-flight sign-in flow. */
|
|
252
|
-
close() {
|
|
253
|
-
this.cancelSignIn();
|
|
254
|
-
this.setView('accounts');
|
|
255
|
-
}
|
|
256
258
|
/** Switch to the "add account" view (the sign-in entry chooser). */
|
|
257
259
|
add() {
|
|
258
260
|
this.setView('add');
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a user-facing blocking reason when an approval payload must not be
|
|
3
|
+
* shown as actionable, or `null` when the request is still pending and valid.
|
|
4
|
+
*/
|
|
5
|
+
export function getCommonsApprovalBlockingReason(info) {
|
|
6
|
+
if (!info.application?.id) {
|
|
7
|
+
return 'The requesting application could not be resolved.';
|
|
8
|
+
}
|
|
9
|
+
if (info.status !== 'pending') {
|
|
10
|
+
return 'This sign-in request is invalid, already used, or expired.';
|
|
11
|
+
}
|
|
12
|
+
const expiresAtMs = parseCommonsApprovalExpiresAt(info.expiresAt);
|
|
13
|
+
if (expiresAtMs !== null && expiresAtMs < Date.now()) {
|
|
14
|
+
return 'This sign-in request has expired. Ask for a new QR code.';
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/** Normalize API `expiresAt` (number or ISO string) to epoch ms. */
|
|
19
|
+
export function parseCommonsApprovalExpiresAt(expiresAt) {
|
|
20
|
+
if (typeof expiresAt === 'number' && Number.isFinite(expiresAt))
|
|
21
|
+
return expiresAt;
|
|
22
|
+
if (typeof expiresAt === 'string') {
|
|
23
|
+
const ms = Date.parse(expiresAt);
|
|
24
|
+
return Number.isFinite(ms) ? ms : null;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|