@oxy.so/core 4.5.0-oxy-one.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/HttpService.js +63 -19
- package/dist/cjs/api/accounts.js +10 -3
- package/dist/cjs/api/agency.js +3 -1
- package/dist/cjs/api/apps.js +15 -4
- package/dist/cjs/api/assets.js +39 -11
- package/dist/cjs/api/auth.js +28 -8
- package/dist/cjs/api/billing.js +24 -7
- package/dist/cjs/api/civic.js +14 -4
- package/dist/cjs/api/follows.js +9 -2
- package/dist/cjs/api/identity.js +31 -9
- package/dist/cjs/api/nodes.js +3 -1
- package/dist/cjs/api/privacy.js +8 -2
- package/dist/cjs/api/session.js +15 -4
- package/dist/cjs/api/store.js +15 -5
- package/dist/cjs/api/topics.js +10 -3
- package/dist/cjs/api/users.js +16 -4
- package/dist/cjs/boot/sessionColdBoot.js +40 -13
- package/dist/cjs/constants/version.js +6 -6
- package/dist/cjs/crypto/identityMarker.js +3 -1
- package/dist/cjs/crypto/identityProof.js +6 -1
- package/dist/cjs/crypto/keyManager.js +87 -32
- package/dist/cjs/crypto/recoveryPhrase.js +2 -2
- package/dist/cjs/crypto/signatureService.js +3 -4
- package/dist/cjs/i18n/index.js +16 -3
- package/dist/cjs/i18n/locales/ar-SA.js +366 -366
- package/dist/cjs/i18n/locales/ca-ES.js +366 -366
- package/dist/cjs/i18n/locales/de-DE.js +366 -366
- package/dist/cjs/i18n/locales/en-US.js +1994 -1994
- package/dist/cjs/i18n/locales/es-ES.js +1994 -1994
- package/dist/cjs/i18n/locales/fr-FR.js +366 -366
- package/dist/cjs/i18n/locales/it-IT.js +366 -366
- package/dist/cjs/i18n/locales/ja-JP.js +366 -366
- package/dist/cjs/i18n/locales/ko-KR.js +366 -366
- package/dist/cjs/i18n/locales/pt-PT.js +366 -366
- package/dist/cjs/i18n/locales/zh-CN.js +366 -366
- package/dist/cjs/inference/OxyInferenceClient.js +12 -18
- package/dist/cjs/models/interfaces.js +11 -11
- package/dist/cjs/server/OxyServer.js +86 -28
- package/dist/cjs/server/agentAccount.js +6 -2
- package/dist/cjs/server/aliaMachineCredential.js +39 -14
- package/dist/cjs/server/auth.js +5 -4
- package/dist/cjs/server/capabilityTicket.js +5 -5
- package/dist/cjs/server/index.js +2 -1
- package/dist/cjs/server/liveCapabilityTicket.js +4 -1
- package/dist/cjs/server/middleware.js +61 -42
- package/dist/cjs/server/namespaces.js +28 -7
- package/dist/cjs/server/requesterAssertion.js +36 -30
- package/dist/cjs/server/serviceTokenLifetime.js +7 -4
- package/dist/cjs/server/traffic.js +53 -14
- package/dist/cjs/server/trafficNodeHttp.js +33 -8
- package/dist/cjs/server/workloadIdentity.client.js +4 -0
- package/dist/cjs/server/workloadIdentity.js +28 -1
- package/dist/cjs/session/SessionClient.js +54 -17
- package/dist/cjs/session/accountDelegationAccess.js +4 -6
- package/dist/cjs/session/accountDialogController.js +1 -1
- package/dist/cjs/session/authStateStore.js +18 -9
- package/dist/cjs/session/identityPin.js +6 -2
- package/dist/cjs/session/refresh.js +81 -23
- package/dist/cjs/session/sharedDeviceCredential.js +28 -15
- package/dist/cjs/shared/utils/errorUtils.js +1 -1
- package/dist/cjs/shared/utils/networkUtils.js +1 -1
- package/dist/cjs/shared/utils/themeUtils.js +1 -1
- package/dist/cjs/utils/accountCacheSweep.js +3 -1
- package/dist/cjs/utils/accountUtils.js +1 -3
- package/dist/cjs/utils/asyncUtils.js +1 -1
- package/dist/cjs/utils/authHelpers.js +2 -1
- package/dist/cjs/utils/avatarUtils.js +4 -2
- package/dist/cjs/utils/cache.js +1 -1
- package/dist/cjs/utils/cacheKey.js +3 -1
- package/dist/cjs/utils/deviceManager.js +4 -4
- package/dist/cjs/utils/errorUtils.js +6 -4
- package/dist/cjs/utils/languageUtils.js +404 -57
- package/dist/cjs/utils/profileLinks.js +1 -3
- package/dist/cjs/utils/requestUtils.js +7 -1
- package/dist/cjs/utils/userIdentity.js +1 -1
- package/dist/cjs/utils/validationUtils.js +5 -6
- package/dist/esm/HttpService.js +64 -20
- package/dist/esm/api/accounts.js +11 -4
- package/dist/esm/api/agency.js +3 -1
- package/dist/esm/api/apps.js +15 -4
- package/dist/esm/api/assets.js +39 -11
- package/dist/esm/api/auth.js +28 -8
- package/dist/esm/api/billing.js +25 -8
- package/dist/esm/api/civic.js +14 -4
- package/dist/esm/api/follows.js +9 -2
- package/dist/esm/api/identity.js +31 -9
- package/dist/esm/api/nodes.js +3 -1
- package/dist/esm/api/privacy.js +8 -2
- package/dist/esm/api/session.js +15 -4
- package/dist/esm/api/store.js +15 -5
- package/dist/esm/api/topics.js +10 -3
- package/dist/esm/api/users.js +16 -4
- package/dist/esm/boot/sessionColdBoot.js +41 -14
- package/dist/esm/constants/version.js +6 -6
- package/dist/esm/crypto/identityMarker.js +3 -1
- package/dist/esm/crypto/identityProof.js +6 -1
- package/dist/esm/crypto/internal.js +1 -1
- package/dist/esm/crypto/keyManager.js +87 -32
- package/dist/esm/crypto/recoveryPhrase.js +2 -2
- package/dist/esm/crypto/signatureService.js +3 -4
- package/dist/esm/i18n/index.js +16 -3
- package/dist/esm/i18n/locales/ar-SA.js +366 -366
- package/dist/esm/i18n/locales/ca-ES.js +366 -366
- package/dist/esm/i18n/locales/de-DE.js +366 -366
- package/dist/esm/i18n/locales/en-US.js +1994 -1994
- package/dist/esm/i18n/locales/es-ES.js +1994 -1994
- package/dist/esm/i18n/locales/fr-FR.js +366 -366
- package/dist/esm/i18n/locales/it-IT.js +366 -366
- package/dist/esm/i18n/locales/ja-JP.js +366 -366
- package/dist/esm/i18n/locales/ko-KR.js +366 -366
- package/dist/esm/i18n/locales/pt-PT.js +366 -366
- package/dist/esm/i18n/locales/zh-CN.js +366 -366
- package/dist/esm/index.js +1 -1
- package/dist/esm/inference/OxyInferenceClient.js +13 -19
- package/dist/esm/models/interfaces.js +11 -11
- package/dist/esm/server/OxyServer.js +87 -29
- package/dist/esm/server/agentAccount.js +6 -2
- package/dist/esm/server/aliaMachineCredential.js +39 -14
- package/dist/esm/server/auth.js +6 -5
- package/dist/esm/server/capabilityTicket.js +6 -6
- package/dist/esm/server/index.js +5 -5
- package/dist/esm/server/liveCapabilityTicket.js +6 -3
- package/dist/esm/server/middleware.js +62 -43
- package/dist/esm/server/namespaces.js +31 -10
- package/dist/esm/server/requesterAssertion.js +36 -30
- package/dist/esm/server/serviceTokenLifetime.js +7 -4
- package/dist/esm/server/traffic.js +55 -16
- package/dist/esm/server/trafficNodeHttp.js +34 -9
- package/dist/esm/server/userInvalidation.js +1 -1
- package/dist/esm/server/workloadIdentity.client.js +3 -0
- package/dist/esm/server/workloadIdentity.js +27 -1
- package/dist/esm/session/SessionClient.js +54 -17
- package/dist/esm/session/accountDelegationAccess.js +5 -7
- package/dist/esm/session/accountDialogController.js +1 -1
- package/dist/esm/session/authStateStore.js +18 -9
- package/dist/esm/session/identityPin.js +6 -2
- package/dist/esm/session/refresh.js +82 -24
- package/dist/esm/session/sharedDeviceCredential.js +28 -15
- package/dist/esm/shared/utils/errorUtils.js +1 -1
- package/dist/esm/shared/utils/networkUtils.js +1 -1
- package/dist/esm/shared/utils/themeUtils.js +1 -1
- package/dist/esm/utils/accountCacheSweep.js +3 -1
- package/dist/esm/utils/accountUtils.js +1 -3
- package/dist/esm/utils/asyncUtils.js +1 -1
- package/dist/esm/utils/authHelpers.js +2 -1
- package/dist/esm/utils/avatarUtils.js +4 -2
- package/dist/esm/utils/cache.js +1 -1
- package/dist/esm/utils/cacheKey.js +3 -1
- package/dist/esm/utils/deviceManager.js +4 -4
- package/dist/esm/utils/errorUtils.js +6 -4
- package/dist/esm/utils/languageUtils.js +404 -57
- package/dist/esm/utils/profileLinks.js +1 -3
- package/dist/esm/utils/requestUtils.js +7 -1
- package/dist/esm/utils/userIdentity.js +1 -1
- package/dist/esm/utils/validationUtils.js +5 -6
- package/dist/types/HttpService.d.ts +1 -1
- package/dist/types/api/accounts.d.ts +1 -1
- package/dist/types/api/apps.d.ts +4 -4
- package/dist/types/crypto/internal.d.ts +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/server/index.d.ts +6 -6
- package/dist/types/server/workloadIdentity.client.d.ts +5 -1
- package/dist/types/server/workloadIdentity.d.ts +26 -0
- package/dist/types/session/accountDelegationAccess.d.ts +1 -1
- package/dist/types/session/index.d.ts +1 -1
- package/dist/types/utils/userIdentity.d.ts +1 -1
- package/dist/types/utils/validationUtils.d.ts +4 -5
- package/package.json +2 -3
- package/src/HttpService.ts +183 -53
- package/src/OxyServices.errors.ts +7 -3
- package/src/OxyServices.ts +6 -2
- package/src/api/accounts.ts +76 -26
- package/src/api/agency.ts +24 -9
- package/src/api/appData.ts +12 -2
- package/src/api/apps.ts +81 -31
- package/src/api/assets.ts +113 -43
- package/src/api/auth.ts +145 -77
- package/src/api/billing.ts +112 -65
- package/src/api/civic.ts +61 -19
- package/src/api/contacts.ts +4 -1
- package/src/api/devices.ts +54 -21
- package/src/api/follows.ts +88 -31
- package/src/api/identity.ts +101 -33
- package/src/api/linkedAccounts.ts +26 -8
- package/src/api/nodes.ts +18 -6
- package/src/api/notifications.ts +26 -6
- package/src/api/privacy.ts +42 -8
- package/src/api/reputation.ts +18 -7
- package/src/api/session.ts +23 -5
- package/src/api/store.ts +81 -31
- package/src/api/topics.ts +18 -6
- package/src/api/users.ts +98 -35
- package/src/boot/sessionColdBoot.ts +60 -19
- package/src/civic/payloads.ts +1 -4
- package/src/client/context.ts +6 -1
- package/src/constants/version.ts +7 -6
- package/src/crypto/aead.ts +1 -5
- package/src/crypto/ecdh.ts +1 -4
- package/src/crypto/identityDerivations.ts +5 -1
- package/src/crypto/identityMarker.ts +3 -1
- package/src/crypto/identityProof.ts +6 -1
- package/src/crypto/index.ts +11 -11
- package/src/crypto/internal.ts +6 -1
- package/src/crypto/keyManager.ts +348 -98
- package/src/crypto/recoveryPhrase.ts +14 -9
- package/src/crypto/registrationPow.ts +1 -1
- package/src/crypto/signatureService.ts +14 -6
- package/src/i18n/accountCategoryLabels.ts +1 -4
- package/src/i18n/accountRoleLabels.ts +1 -4
- package/src/i18n/index.ts +25 -7
- package/src/i18n/locales/ar-SA.ts +375 -366
- package/src/i18n/locales/ca-ES.ts +382 -366
- package/src/i18n/locales/de-DE.ts +387 -366
- package/src/i18n/locales/en-US.ts +2034 -1994
- package/src/i18n/locales/es-ES.ts +2043 -1994
- package/src/i18n/locales/fr-FR.ts +387 -366
- package/src/i18n/locales/it-IT.ts +380 -366
- package/src/i18n/locales/ja-JP.ts +380 -366
- package/src/i18n/locales/ko-KR.ts +377 -366
- package/src/i18n/locales/pt-PT.ts +383 -366
- package/src/i18n/locales/zh-CN.ts +373 -366
- package/src/i18n/trustTierLabels.ts +1 -4
- package/src/index.ts +371 -349
- package/src/inference/OxyInferenceClient.ts +784 -780
- package/src/models/interfaces.ts +30 -27
- package/src/server/OxyServer.ts +227 -68
- package/src/server/agentAccount.ts +8 -2
- package/src/server/aliaMachineCredential.ts +57 -16
- package/src/server/auth.ts +9 -5
- package/src/server/capabilityTicket.ts +30 -21
- package/src/server/index.ts +29 -6
- package/src/server/liveCapabilityTicket.ts +31 -9
- package/src/server/middleware.ts +212 -121
- package/src/server/namespaces.ts +247 -94
- package/src/server/rateLimit.ts +2 -4
- package/src/server/requesterAssertion.ts +93 -54
- package/src/server/serviceTokenLifetime.ts +9 -4
- package/src/server/traffic.ts +117 -23
- package/src/server/trafficNodeHttp.ts +61 -14
- package/src/server/userInvalidation.ts +1 -4
- package/src/server/workloadIdentity.client.ts +12 -1
- package/src/server/workloadIdentity.ts +52 -5
- package/src/session/SessionClient.ts +170 -49
- package/src/session/accountDelegationAccess.ts +24 -31
- package/src/session/accountDialogController.ts +59 -14
- package/src/session/accountSwitchTargets.ts +10 -15
- package/src/session/authStateStore.ts +121 -81
- package/src/session/deviceDirectory.ts +1 -3
- package/src/session/identityPin.ts +10 -11
- package/src/session/identitySession.ts +3 -1
- package/src/session/index.ts +87 -82
- package/src/session/refresh.ts +138 -44
- package/src/session/sessionClientHost.ts +1 -3
- package/src/session/sharedDeviceCredential.ts +35 -14
- package/src/session/socketLoader.ts +9 -2
- package/src/shared/utils/errorUtils.ts +5 -2
- package/src/shared/utils/networkUtils.ts +5 -8
- package/src/shared/utils/themeUtils.ts +2 -6
- package/src/types/buffer.d.ts +66 -21
- package/src/types/expo-secure-store.d.ts +5 -1
- package/src/utils/accountCacheSweep.ts +4 -5
- package/src/utils/accountUtils.ts +107 -108
- package/src/utils/apiUtils.ts +3 -3
- package/src/utils/asyncUtils.ts +2 -2
- package/src/utils/authHelpers.ts +10 -5
- package/src/utils/avatarUtils.ts +11 -6
- package/src/utils/cache.ts +9 -10
- package/src/utils/cacheKey.ts +5 -1
- package/src/utils/coldBoot.ts +1 -3
- package/src/utils/commonsDelivery.ts +1 -4
- package/src/utils/deviceManager.ts +15 -14
- package/src/utils/errorUtils.ts +68 -86
- package/src/utils/languageUtils.ts +407 -58
- package/src/utils/oauthPkce.ts +1 -4
- package/src/utils/platform.ts +0 -1
- package/src/utils/profileLinks.ts +2 -5
- package/src/utils/requestUtils.ts +18 -14
- package/src/utils/sessionUtils.ts +34 -33
- package/src/utils/userHandle.ts +3 -1
- package/src/utils/userIdentity.ts +1 -1
- package/src/utils/validationUtils.ts +11 -9
|
@@ -807,10 +807,18 @@ export class AccountDialogController {
|
|
|
807
807
|
// unexpected — surface it, and keep whatever directory is already held so
|
|
808
808
|
// an outage degrades rather than blanks the switcher.
|
|
809
809
|
if (extractErrorStatus(error) === 401) {
|
|
810
|
-
logger.debug(
|
|
810
|
+
logger.debug(
|
|
811
|
+
'[AccountDialogController] directory unauthorized (signed out)',
|
|
812
|
+
{ component: 'AccountDialogController' },
|
|
813
|
+
error,
|
|
814
|
+
);
|
|
811
815
|
} else {
|
|
812
816
|
failure = errorMessage(error);
|
|
813
|
-
logger.warn(
|
|
817
|
+
logger.warn(
|
|
818
|
+
'[AccountDialogController] directory refresh failed',
|
|
819
|
+
{ component: 'AccountDialogController' },
|
|
820
|
+
error,
|
|
821
|
+
);
|
|
814
822
|
}
|
|
815
823
|
}
|
|
816
824
|
// Superseded by a newer refresh: that read owns `error` and `loading`, so an
|
|
@@ -1041,7 +1049,11 @@ export class AccountDialogController {
|
|
|
1041
1049
|
if (!this.isCurrentAttempt(attempt)) return;
|
|
1042
1050
|
// The identity mint failed — log and fall through to the QR handoff rather
|
|
1043
1051
|
// than dead-ending the sign-in.
|
|
1044
|
-
logger.warn(
|
|
1052
|
+
logger.warn(
|
|
1053
|
+
'[AccountDialogController] signInWithCommonsIdentity failed',
|
|
1054
|
+
{ component: 'AccountDialogController' },
|
|
1055
|
+
error,
|
|
1056
|
+
);
|
|
1045
1057
|
}
|
|
1046
1058
|
await this.startDeviceFlowSession(attempt, { deliver: true });
|
|
1047
1059
|
}
|
|
@@ -1127,7 +1139,10 @@ export class AccountDialogController {
|
|
|
1127
1139
|
): Promise<CommonsSignInHandle | null> {
|
|
1128
1140
|
if (!this.isCurrentAttempt(attempt)) return null;
|
|
1129
1141
|
if (!this.clientId) {
|
|
1130
|
-
this.failSignIn(
|
|
1142
|
+
this.failSignIn(
|
|
1143
|
+
'not-configured',
|
|
1144
|
+
'This app is not configured for sign-in (missing clientId).',
|
|
1145
|
+
);
|
|
1131
1146
|
return null;
|
|
1132
1147
|
}
|
|
1133
1148
|
this.setSignIn({ ...IDLE_SIGN_IN_FACTS, phase: 'starting' });
|
|
@@ -1235,7 +1250,10 @@ export class AccountDialogController {
|
|
|
1235
1250
|
* surfaced as an error: there is nothing the user did wrong and nothing for
|
|
1236
1251
|
* them to fix.
|
|
1237
1252
|
*/
|
|
1238
|
-
private async deliverToKnownCommons(
|
|
1253
|
+
private async deliverToKnownCommons(
|
|
1254
|
+
authorizeCode: string,
|
|
1255
|
+
commonsAvailable: boolean,
|
|
1256
|
+
): Promise<number> {
|
|
1239
1257
|
// Route 1 (mobile with a verified local Commons link) reaches the identity
|
|
1240
1258
|
// on this very device — pushing as well would notify a second surface for a
|
|
1241
1259
|
// request the user is about to confirm here.
|
|
@@ -1397,12 +1415,16 @@ export class AccountDialogController {
|
|
|
1397
1415
|
// recording it before the terminal branches means a poll that also
|
|
1398
1416
|
// carries the approval still leaves an honest trail behind it.
|
|
1399
1417
|
this.recordDeliveryProgress(status.pushSentAt, status.openedAt);
|
|
1400
|
-
const purpose =
|
|
1418
|
+
const purpose =
|
|
1419
|
+
status.purpose === 'oauth_authorization' ? 'oauth_authorization' : 'device_sign_in';
|
|
1401
1420
|
if (status.authorized && purpose === 'oauth_authorization') {
|
|
1402
1421
|
// OAuth-bound sessions mint no sessionId on approval — they finalize
|
|
1403
1422
|
// into an authorization code. The account dialog only starts device
|
|
1404
1423
|
// sign-in today; stop rather than poll until expiry.
|
|
1405
|
-
this.failSignIn(
|
|
1424
|
+
this.failSignIn(
|
|
1425
|
+
'unsupported-flow',
|
|
1426
|
+
"This sign-in flow cannot be completed here. Use the app's OAuth sign-in instead.",
|
|
1427
|
+
);
|
|
1406
1428
|
return;
|
|
1407
1429
|
}
|
|
1408
1430
|
if (status.authorized && status.sessionId) {
|
|
@@ -1420,7 +1442,11 @@ export class AccountDialogController {
|
|
|
1420
1442
|
}
|
|
1421
1443
|
} catch (error) {
|
|
1422
1444
|
// Transient poll error — the next tick retries. Logged, never thrown.
|
|
1423
|
-
logger.debug(
|
|
1445
|
+
logger.debug(
|
|
1446
|
+
'[AccountDialogController] poll error (will retry)',
|
|
1447
|
+
{ component: 'AccountDialogController' },
|
|
1448
|
+
error,
|
|
1449
|
+
);
|
|
1424
1450
|
}
|
|
1425
1451
|
if (this.signInToken === sessionToken) {
|
|
1426
1452
|
this.scheduleNextPoll(sessionToken);
|
|
@@ -1445,7 +1471,11 @@ export class AccountDialogController {
|
|
|
1445
1471
|
this.patchSignIn({ pushSentAt: nextPushSentAt, openedAt: nextOpenedAt });
|
|
1446
1472
|
}
|
|
1447
1473
|
|
|
1448
|
-
private async claimAndComplete(
|
|
1474
|
+
private async claimAndComplete(
|
|
1475
|
+
attempt: number,
|
|
1476
|
+
sessionId: string,
|
|
1477
|
+
sessionToken: string,
|
|
1478
|
+
): Promise<void> {
|
|
1449
1479
|
this.patchSignIn({ phase: 'authorized' });
|
|
1450
1480
|
let claimed: {
|
|
1451
1481
|
accessToken: string;
|
|
@@ -1466,7 +1496,10 @@ export class AccountDialogController {
|
|
|
1466
1496
|
}
|
|
1467
1497
|
if (!this.isCurrentAttempt(attempt)) return;
|
|
1468
1498
|
if (!claimed?.accessToken || !claimed.user) {
|
|
1469
|
-
this.failSignIn(
|
|
1499
|
+
this.failSignIn(
|
|
1500
|
+
'claim-failed',
|
|
1501
|
+
'Authorization succeeded but the session could not be claimed. Please try again.',
|
|
1502
|
+
);
|
|
1470
1503
|
return;
|
|
1471
1504
|
}
|
|
1472
1505
|
// `SessionLoginResponse.user` is the minimal session-carried shape; the claim
|
|
@@ -1541,7 +1574,7 @@ export class AccountDialogController {
|
|
|
1541
1574
|
this.signInToken = null;
|
|
1542
1575
|
this.clearPollTimer();
|
|
1543
1576
|
this.closeAuthSessionSocket();
|
|
1544
|
-
|
|
1577
|
+
// Terminal SUCCESS, not idle: the surface gets one honest frame to show
|
|
1545
1578
|
// "Identity confirmed" before it closes. Cleared on the next view change.
|
|
1546
1579
|
this.signIn = this.stampSignIn(COMPLETED_SIGN_IN_FACTS);
|
|
1547
1580
|
this.view = 'accounts';
|
|
@@ -1617,7 +1650,11 @@ export class AccountDialogController {
|
|
|
1617
1650
|
});
|
|
1618
1651
|
} catch (error) {
|
|
1619
1652
|
// Socket unavailable — the fallback poll still completes the flow.
|
|
1620
|
-
logger.debug(
|
|
1653
|
+
logger.debug(
|
|
1654
|
+
'[AccountDialogController] auth-session socket create failed (poll fallback)',
|
|
1655
|
+
{ component: 'AccountDialogController' },
|
|
1656
|
+
error,
|
|
1657
|
+
);
|
|
1621
1658
|
return;
|
|
1622
1659
|
}
|
|
1623
1660
|
const join = (): void => {
|
|
@@ -1625,7 +1662,11 @@ export class AccountDialogController {
|
|
|
1625
1662
|
try {
|
|
1626
1663
|
socket.emit('join', sessionToken);
|
|
1627
1664
|
} catch (error) {
|
|
1628
|
-
logger.debug(
|
|
1665
|
+
logger.debug(
|
|
1666
|
+
'[AccountDialogController] auth-session join failed',
|
|
1667
|
+
{ component: 'AccountDialogController' },
|
|
1668
|
+
error,
|
|
1669
|
+
);
|
|
1629
1670
|
}
|
|
1630
1671
|
};
|
|
1631
1672
|
socket.on('connect', join);
|
|
@@ -1648,7 +1689,11 @@ export class AccountDialogController {
|
|
|
1648
1689
|
socket.off('connect');
|
|
1649
1690
|
socket.disconnect();
|
|
1650
1691
|
} catch (error) {
|
|
1651
|
-
logger.debug(
|
|
1692
|
+
logger.debug(
|
|
1693
|
+
'[AccountDialogController] auth-session socket close failed',
|
|
1694
|
+
{ component: 'AccountDialogController' },
|
|
1695
|
+
error,
|
|
1696
|
+
);
|
|
1652
1697
|
}
|
|
1653
1698
|
}
|
|
1654
1699
|
|
|
@@ -17,11 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { isOperatorSwitchTargetKind } from '@oxy.so/contracts';
|
|
20
|
-
import type {
|
|
21
|
-
AccountRelationship,
|
|
22
|
-
AccountKind,
|
|
23
|
-
AccountMember,
|
|
24
|
-
} from '../api/accounts';
|
|
20
|
+
import type { AccountRelationship, AccountKind, AccountMember } from '../api/accounts';
|
|
25
21
|
|
|
26
22
|
/**
|
|
27
23
|
* Whether the caller can BECOME this account — the one question every account
|
|
@@ -55,9 +51,10 @@ import type {
|
|
|
55
51
|
* Takes a structural subset rather than a whole {@link AccountNode} so a caller
|
|
56
52
|
* holding an already-projected row can ask it too.
|
|
57
53
|
*/
|
|
58
|
-
export function isSwitchTargetAccount(
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
export function isSwitchTargetAccount(node: {
|
|
55
|
+
kind?: AccountKind | null;
|
|
56
|
+
relationship?: AccountRelationship;
|
|
57
|
+
}): boolean {
|
|
61
58
|
return node.relationship === 'self' || isOperatorSwitchTargetKind(node.kind);
|
|
62
59
|
}
|
|
63
60
|
|
|
@@ -72,13 +69,11 @@ export function isSwitchTargetAccount(
|
|
|
72
69
|
* API always resolves effective permissions for owned accounts, but test
|
|
73
70
|
* fixtures and stale rows may omit the membership blob.
|
|
74
71
|
*/
|
|
75
|
-
export function canSwitchIntoAccount(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
): boolean {
|
|
72
|
+
export function canSwitchIntoAccount(node: {
|
|
73
|
+
kind?: AccountKind | null;
|
|
74
|
+
relationship?: AccountRelationship;
|
|
75
|
+
callerMembership?: AccountMember | null;
|
|
76
|
+
}): boolean {
|
|
82
77
|
if (node.relationship === 'self') {
|
|
83
78
|
return true;
|
|
84
79
|
}
|
|
@@ -89,11 +89,14 @@ export interface AuthStateStore {
|
|
|
89
89
|
clear(): Promise<void>;
|
|
90
90
|
/** Durable local intent only: never authority or a mutation of an identity key. */
|
|
91
91
|
isAutomaticIdentitySignInSuppressed?(): Promise<boolean>;
|
|
92
|
-
setAutomaticIdentitySignInSuppressed?(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
setAutomaticIdentitySignInSuppressed?(
|
|
93
|
+
suppressed: boolean,
|
|
94
|
+
guard?: {
|
|
95
|
+
/** Checked inside the native store queue, before mutating logout intent. */
|
|
96
|
+
expectedState: PersistedAuthState;
|
|
97
|
+
isCurrent: () => boolean;
|
|
98
|
+
},
|
|
99
|
+
): Promise<boolean>;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
export interface AuthStateWriteGuard {
|
|
@@ -101,11 +104,19 @@ export interface AuthStateWriteGuard {
|
|
|
101
104
|
isCurrent: () => boolean;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
export function matchesAuthState(
|
|
107
|
+
export function matchesAuthState(
|
|
108
|
+
actual: PersistedAuthState | null,
|
|
109
|
+
expected: PersistedAuthState | null,
|
|
110
|
+
): boolean {
|
|
105
111
|
if (!actual || !expected) return actual === expected;
|
|
106
|
-
return
|
|
107
|
-
actual.
|
|
108
|
-
actual.
|
|
112
|
+
return (
|
|
113
|
+
actual.deviceId === expected.deviceId &&
|
|
114
|
+
actual.deviceSecret === expected.deviceSecret &&
|
|
115
|
+
actual.sessionId === expected.sessionId &&
|
|
116
|
+
actual.userId === expected.userId &&
|
|
117
|
+
actual.accessToken === expected.accessToken &&
|
|
118
|
+
actual.expiresAt === expected.expiresAt
|
|
119
|
+
);
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
/**
|
|
@@ -136,7 +147,8 @@ export interface NativeKeyValueStorage {
|
|
|
136
147
|
* so upgrading users are not signed out; the next `save()` splits them apart.
|
|
137
148
|
*/
|
|
138
149
|
export const AUTH_STATE_STORAGE_KEY = 'oxy.auth.v1';
|
|
139
|
-
export const AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY =
|
|
150
|
+
export const AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY =
|
|
151
|
+
'oxy.auth.automatic-identity-signin-suppressed.v1';
|
|
140
152
|
|
|
141
153
|
/**
|
|
142
154
|
* Versioned BEST-EFFORT warm-token storage key. Holds the short-lived
|
|
@@ -277,7 +289,10 @@ function serializeWarmToken(state: PersistedAuthState): string | null {
|
|
|
277
289
|
* present; when the warm key is ABSENT the token fields fall back to whatever
|
|
278
290
|
* the durable blob carried — the pre-split combined `oxy.auth.v1` blob (BACK-COMPAT).
|
|
279
291
|
*/
|
|
280
|
-
function composeState(
|
|
292
|
+
function composeState(
|
|
293
|
+
durableRaw: string | null,
|
|
294
|
+
warmRaw: string | null,
|
|
295
|
+
): PersistedAuthState | null {
|
|
281
296
|
const state = deserialize(durableRaw);
|
|
282
297
|
if (!state) {
|
|
283
298
|
return null;
|
|
@@ -459,13 +474,17 @@ export function createNativeAuthStateStore(storage: NativeKeyValueStorage): Auth
|
|
|
459
474
|
let pending: Promise<void> = Promise.resolve();
|
|
460
475
|
const serialize = <T>(operation: () => Promise<T>): Promise<T> => {
|
|
461
476
|
const result = pending.then(operation);
|
|
462
|
-
pending = result.then(
|
|
477
|
+
pending = result.then(
|
|
478
|
+
() => undefined,
|
|
479
|
+
() => undefined,
|
|
480
|
+
);
|
|
463
481
|
return result;
|
|
464
482
|
};
|
|
465
483
|
const readCurrent = async (): Promise<PersistedAuthState | null> => {
|
|
466
484
|
if (sessionMirror !== undefined) return sessionMirror;
|
|
467
485
|
const [durable, warm] = await Promise.all([
|
|
468
|
-
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
486
|
+
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
487
|
+
storage.getItem(AUTH_STATE_TOKEN_STORAGE_KEY),
|
|
469
488
|
]);
|
|
470
489
|
return composeState(durable, warm);
|
|
471
490
|
};
|
|
@@ -513,75 +532,96 @@ export function createNativeAuthStateStore(storage: NativeKeyValueStorage): Auth
|
|
|
513
532
|
return durablePersisted;
|
|
514
533
|
};
|
|
515
534
|
return {
|
|
516
|
-
isAutomaticIdentitySignInSuppressed: () =>
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}),
|
|
524
|
-
setAutomaticIdentitySignInSuppressed: (suppressed, guard) => serialize(async () => {
|
|
525
|
-
if (guard) {
|
|
526
|
-
let current = sessionMirror;
|
|
527
|
-
if (current === undefined) {
|
|
528
|
-
try {
|
|
529
|
-
const [durable, warm] = await Promise.all([
|
|
530
|
-
storage.getItem(AUTH_STATE_STORAGE_KEY), storage.getItem(AUTH_STATE_TOKEN_STORAGE_KEY),
|
|
531
|
-
]);
|
|
532
|
-
current = composeState(durable, warm);
|
|
533
|
-
} catch { suppressedMirror = true; return false; }
|
|
535
|
+
isAutomaticIdentitySignInSuppressed: () =>
|
|
536
|
+
serialize(async () => {
|
|
537
|
+
if (suppressedMirror !== undefined) return suppressedMirror;
|
|
538
|
+
try {
|
|
539
|
+
return (await storage.getItem(AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY)) !== null;
|
|
540
|
+
} catch {
|
|
541
|
+
return true; // Unknown storage must not start a new key sign-in.
|
|
534
542
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
543
|
+
}),
|
|
544
|
+
setAutomaticIdentitySignInSuppressed: (suppressed, guard) =>
|
|
545
|
+
serialize(async () => {
|
|
546
|
+
if (guard) {
|
|
547
|
+
let current = sessionMirror;
|
|
548
|
+
if (current === undefined) {
|
|
549
|
+
try {
|
|
550
|
+
const [durable, warm] = await Promise.all([
|
|
551
|
+
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
552
|
+
storage.getItem(AUTH_STATE_TOKEN_STORAGE_KEY),
|
|
553
|
+
]);
|
|
554
|
+
current = composeState(durable, warm);
|
|
555
|
+
} catch {
|
|
556
|
+
suppressedMirror = true;
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const expected = guard.expectedState;
|
|
561
|
+
if (
|
|
562
|
+
!guard.isCurrent() ||
|
|
563
|
+
!current ||
|
|
564
|
+
current.deviceId !== expected.deviceId ||
|
|
565
|
+
current.deviceSecret !== expected.deviceSecret ||
|
|
566
|
+
current.sessionId !== expected.sessionId ||
|
|
567
|
+
current.userId !== expected.userId ||
|
|
568
|
+
current.accessToken !== expected.accessToken ||
|
|
569
|
+
current.expiresAt !== expected.expiresAt
|
|
570
|
+
)
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
suppressedMirror = suppressed;
|
|
574
|
+
try {
|
|
575
|
+
if (suppressed) await storage.setItem(AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY, '1');
|
|
576
|
+
else await storage.removeItem(AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY);
|
|
577
|
+
const actual = await storage.getItem(AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY);
|
|
578
|
+
const verified = suppressed ? actual === '1' : actual === null;
|
|
579
|
+
if (!verified) suppressedMirror = true;
|
|
580
|
+
return verified;
|
|
581
|
+
} catch {
|
|
582
|
+
suppressedMirror = true;
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}),
|
|
586
|
+
load: () =>
|
|
587
|
+
serialize(async () => {
|
|
588
|
+
if (sessionMirror !== undefined) {
|
|
589
|
+
return sessionMirror;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
const [durableRaw, warmRaw] = await Promise.all([
|
|
593
|
+
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
594
|
+
storage.getItem(AUTH_STATE_TOKEN_STORAGE_KEY),
|
|
595
|
+
]);
|
|
596
|
+
return composeState(durableRaw, warmRaw);
|
|
597
|
+
} catch {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
}),
|
|
565
601
|
save: (state) => serialize(() => persistState(state)),
|
|
566
|
-
saveIfCurrent: (state, guard) =>
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
602
|
+
saveIfCurrent: (state, guard) =>
|
|
603
|
+
serialize(async () => {
|
|
604
|
+
try {
|
|
605
|
+
const current = await readCurrent();
|
|
606
|
+
if (!guard.isCurrent() || !matchesAuthState(current, guard.expectedState)) return false;
|
|
607
|
+
} catch {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
return persistState(state);
|
|
611
|
+
}),
|
|
612
|
+
clear: () =>
|
|
613
|
+
serialize(async () => {
|
|
614
|
+
sessionMirror = null;
|
|
615
|
+
try {
|
|
616
|
+
await storage.removeItem(AUTH_STATE_STORAGE_KEY);
|
|
617
|
+
} catch {
|
|
618
|
+
// Non-fatal.
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
await storage.removeItem(AUTH_STATE_TOKEN_STORAGE_KEY);
|
|
622
|
+
} catch {
|
|
623
|
+
// Non-fatal.
|
|
624
|
+
}
|
|
625
|
+
}),
|
|
586
626
|
};
|
|
587
627
|
}
|
|
@@ -179,9 +179,7 @@ export interface DevicePrincipalGroup {
|
|
|
179
179
|
* and rendering them with nothing under them is how "sign out of this person"
|
|
180
180
|
* stays reachable. Silently omitting them would strand the row.
|
|
181
181
|
*/
|
|
182
|
-
export function projectDevicePrincipals(
|
|
183
|
-
directory: DeviceDirectory | null,
|
|
184
|
-
): DevicePrincipalGroup[] {
|
|
182
|
+
export function projectDevicePrincipals(directory: DeviceDirectory | null): DevicePrincipalGroup[] {
|
|
185
183
|
if (directory === null) {
|
|
186
184
|
return [];
|
|
187
185
|
}
|
|
@@ -135,7 +135,10 @@ function serialize(pin: IdentityPin): string | null {
|
|
|
135
135
|
* definitive "no identity here" and therefore never a match — the caller clears
|
|
136
136
|
* the pin rather than binding to an account whose key is gone.
|
|
137
137
|
*/
|
|
138
|
-
export function identityPinMatches(
|
|
138
|
+
export function identityPinMatches(
|
|
139
|
+
pin: IdentityPin | null,
|
|
140
|
+
localPublicKey: string | null,
|
|
141
|
+
): boolean {
|
|
139
142
|
if (!pin || !localPublicKey) {
|
|
140
143
|
return false;
|
|
141
144
|
}
|
|
@@ -208,11 +211,9 @@ export function createWebIdentityPinStore(): IdentityPinStore {
|
|
|
208
211
|
save: async (pin) => {
|
|
209
212
|
const json = serialize(pin);
|
|
210
213
|
if (!json) {
|
|
211
|
-
logger.error(
|
|
212
|
-
'
|
|
213
|
-
|
|
214
|
-
{ component: 'identityPin' },
|
|
215
|
-
);
|
|
214
|
+
logger.error('[identityPin] refusing to persist a malformed identity pin', undefined, {
|
|
215
|
+
component: 'identityPin',
|
|
216
|
+
});
|
|
216
217
|
return false;
|
|
217
218
|
}
|
|
218
219
|
sessionMirror = { publicKey: pin.publicKey.toLowerCase(), accountId: pin.accountId };
|
|
@@ -268,11 +269,9 @@ export function createNativeIdentityPinStore(storage: NativeKeyValueStorage): Id
|
|
|
268
269
|
save: async (pin) => {
|
|
269
270
|
const json = serialize(pin);
|
|
270
271
|
if (!json) {
|
|
271
|
-
logger.error(
|
|
272
|
-
'
|
|
273
|
-
|
|
274
|
-
{ component: 'identityPin' },
|
|
275
|
-
);
|
|
272
|
+
logger.error('[identityPin] refusing to persist a malformed identity pin', undefined, {
|
|
273
|
+
component: 'identityPin',
|
|
274
|
+
});
|
|
276
275
|
return false;
|
|
277
276
|
}
|
|
278
277
|
sessionMirror = { publicKey: pin.publicKey.toLowerCase(), accountId: pin.accountId };
|
|
@@ -159,7 +159,9 @@ export async function establishIdentitySession(args: {
|
|
|
159
159
|
// The signer disagrees with the key we resolved a challenge for — the
|
|
160
160
|
// identity changed mid-flight, or a custom signer was misconfigured. Refuse:
|
|
161
161
|
// verifying under a different key would bind this client to another account.
|
|
162
|
-
throw new Error(
|
|
162
|
+
throw new Error(
|
|
163
|
+
'Identity sign-in aborted: the signing key does not match the device identity key',
|
|
164
|
+
);
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
// `signed.challenge` carries the SIGNATURE (mirrors `signChallenge`).
|