@oxy.so/core 4.5.0-oxy-one.1 → 4.6.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 +16 -3
- 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/models/interfaces.d.ts +9 -1
- 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 +3 -4
- package/src/HttpService.ts +183 -53
- package/src/OxyServices.errors.ts +7 -3
- package/src/OxyServices.ts +6 -2
- package/src/api/accounts.ts +96 -28
- 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 +39 -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
|
@@ -2,9 +2,11 @@ import http from 'node:http';
|
|
|
2
2
|
import https from 'node:https';
|
|
3
3
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
4
|
import { syncBuiltinESMExports } from 'node:module';
|
|
5
|
-
import { normalizeInfrastructureRegion, trafficType } from '@oxy.so/telemetry/collector';
|
|
5
|
+
import { normalizeInfrastructureRegion, trafficType, } from '@oxy.so/telemetry/collector';
|
|
6
6
|
const suppressed = new AsyncLocalStorage();
|
|
7
|
-
export function withoutNodeHttpObservation(callback) {
|
|
7
|
+
export function withoutNodeHttpObservation(callback) {
|
|
8
|
+
return suppressed.run(true, callback);
|
|
9
|
+
}
|
|
8
10
|
/** Node and Bun share the HTTP facade; Bun does not emit Node's HTTP diagnostic channels. */
|
|
9
11
|
export function observeNodeHttp(collector, service, region, resolveEndpoint) {
|
|
10
12
|
const seen = new WeakSet();
|
|
@@ -22,11 +24,23 @@ export function observeNodeHttp(collector, service, region, resolveEndpoint) {
|
|
|
22
24
|
const peer = resolveEndpoint(url);
|
|
23
25
|
if (peer && !request.headersSent)
|
|
24
26
|
request.setHeader('X-Oxy-Source-Region', region);
|
|
25
|
-
const flow = {
|
|
27
|
+
const flow = {
|
|
28
|
+
service,
|
|
29
|
+
region,
|
|
30
|
+
activityType: trafficType(url.pathname),
|
|
31
|
+
scope: peer ? 'internal' : 'external',
|
|
32
|
+
direction: 'outbound',
|
|
33
|
+
sourceRegion: region,
|
|
34
|
+
sourceService: service,
|
|
35
|
+
targetService: peer?.service,
|
|
36
|
+
};
|
|
26
37
|
const emit = request.emit;
|
|
27
38
|
let recorded = false;
|
|
28
|
-
const restore = () => {
|
|
29
|
-
request.emit
|
|
39
|
+
const restore = () => {
|
|
40
|
+
if (request.emit === observedEmit)
|
|
41
|
+
request.emit = emit;
|
|
42
|
+
restoreRequests.delete(restore);
|
|
43
|
+
};
|
|
30
44
|
// Intercept notifications without adding an 'error' listener: observation
|
|
31
45
|
// must not turn an unhandled application error into a swallowed error.
|
|
32
46
|
const observedEmit = function (event, ...args) {
|
|
@@ -34,16 +48,27 @@ export function observeNodeHttp(collector, service, region, resolveEndpoint) {
|
|
|
34
48
|
if (!recorded && event === 'response') {
|
|
35
49
|
recorded = true;
|
|
36
50
|
const response = args[0];
|
|
37
|
-
const targetRegion = peer
|
|
51
|
+
const targetRegion = peer
|
|
52
|
+
? normalizeInfrastructureRegion(response.headers['x-oxy-region'])
|
|
53
|
+
: undefined;
|
|
38
54
|
collector.record({ ...flow, targetRegion });
|
|
39
|
-
collector.record({
|
|
55
|
+
collector.record({
|
|
56
|
+
...flow,
|
|
57
|
+
direction: 'inbound',
|
|
58
|
+
sourceRegion: targetRegion,
|
|
59
|
+
targetRegion: region,
|
|
60
|
+
sourceService: peer?.service,
|
|
61
|
+
targetService: service,
|
|
62
|
+
});
|
|
40
63
|
}
|
|
41
64
|
else if (!recorded && (event === 'error' || event === 'close')) {
|
|
42
65
|
recorded = true;
|
|
43
66
|
collector.record(flow);
|
|
44
67
|
}
|
|
45
68
|
}
|
|
46
|
-
catch {
|
|
69
|
+
catch {
|
|
70
|
+
/* A metrics failure cannot change a request's behavior. */
|
|
71
|
+
}
|
|
47
72
|
if (event === 'close')
|
|
48
73
|
restore();
|
|
49
74
|
return emit.call(this, event, ...args);
|
|
@@ -60,7 +85,7 @@ export function observeNodeHttp(collector, service, region, resolveEndpoint) {
|
|
|
60
85
|
return request;
|
|
61
86
|
}
|
|
62
87
|
};
|
|
63
|
-
const patches = [http, https].map(module => {
|
|
88
|
+
const patches = [http, https].map((module) => {
|
|
64
89
|
const request = module.request;
|
|
65
90
|
const get = module.get;
|
|
66
91
|
const wrappedRequest = ((...args) => safelyObserve(request.apply(module, args)));
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* Node-only; exported solely from `@oxy.so/core/server`.
|
|
52
52
|
*/
|
|
53
53
|
import { OXY_USER_INVALIDATION_CHANNEL, isPublishedOxyUserChangeReason, oxyUserInvalidationEventSchema, } from '@oxy.so/contracts';
|
|
54
|
-
import { evictOxyIdentityCache
|
|
54
|
+
import { evictOxyIdentityCache } from '../utils/identityCacheSweep.js';
|
|
55
55
|
/**
|
|
56
56
|
* Broadcast that an Oxy user's record changed.
|
|
57
57
|
*
|
|
@@ -5,3 +5,6 @@ export function canAttestWorkloadIdentity() {
|
|
|
5
5
|
export async function requestWorkloadServiceToken(_options) {
|
|
6
6
|
throw new Error('Workload identity is only available on a Node host');
|
|
7
7
|
}
|
|
8
|
+
export async function attestWorkloadIdentity(_options) {
|
|
9
|
+
throw new Error('Workload identity is only available on a Node host');
|
|
10
|
+
}
|
|
@@ -81,7 +81,10 @@ function sha256Hex(value) {
|
|
|
81
81
|
* does not cover can be swapped by whoever captured the attestation.
|
|
82
82
|
*/
|
|
83
83
|
function signGetCallerIdentity(credentials, nonce, now) {
|
|
84
|
-
const amzDate = now
|
|
84
|
+
const amzDate = now
|
|
85
|
+
.toISOString()
|
|
86
|
+
.replace(/[-:]/g, '')
|
|
87
|
+
.replace(/\.\d{3}/, '');
|
|
85
88
|
const dateStamp = amzDate.slice(0, 8);
|
|
86
89
|
const headers = {
|
|
87
90
|
host: STS_HOST,
|
|
@@ -116,6 +119,29 @@ async function awsContainerAttestation(fetchImpl, nonce) {
|
|
|
116
119
|
const credentials = await containerCredentials(fetchImpl);
|
|
117
120
|
return { headers: signGetCallerIdentity(credentials, nonce, new Date()) };
|
|
118
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Signs this process's identity proof over a nonce the CALLER chose, and sends
|
|
124
|
+
* it nowhere.
|
|
125
|
+
*
|
|
126
|
+
* {@link requestWorkloadServiceToken} is the ordinary consumer: Oxy issues the
|
|
127
|
+
* nonce and replays the proof. This exists for the one host that is its own
|
|
128
|
+
* verifier — Oxy's API acting as an application it hosts (Inbox) needs to learn
|
|
129
|
+
* which role it runs as, and the honest way to learn that is the way it learns
|
|
130
|
+
* anyone else's: sign `GetCallerIdentity` and let STS say whose signature it is.
|
|
131
|
+
* Reading a role name out of configuration would be the process's own word for
|
|
132
|
+
* what it is, which is what ADR 0026 refuses from every other caller.
|
|
133
|
+
*
|
|
134
|
+
* The same signer, so the two can never disagree about the shape the verifier
|
|
135
|
+
* accepts. Throws exactly as the token path does when there is no identity here.
|
|
136
|
+
*/
|
|
137
|
+
export async function attestWorkloadIdentity(options) {
|
|
138
|
+
if (!options.nonce)
|
|
139
|
+
throw new Error('A workload attestation needs a nonce to sign.');
|
|
140
|
+
return {
|
|
141
|
+
provider: 'aws-iam',
|
|
142
|
+
attestation: await awsContainerAttestation(options.fetch ?? fetch, options.nonce),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
119
145
|
/**
|
|
120
146
|
* Exchanges this process's identity for an Oxy service token.
|
|
121
147
|
*
|
|
@@ -249,7 +249,8 @@ export class SessionClient {
|
|
|
249
249
|
// otherwise, so a mismatch always resolves to "mint".
|
|
250
250
|
const needsMintBeforeNotify = transport != null &&
|
|
251
251
|
next.accounts.length > 0 &&
|
|
252
|
-
(activeAccountId === null ||
|
|
252
|
+
(activeAccountId === null ||
|
|
253
|
+
computeIdentityTag(this.host.getAccessToken()) !== activeAccountId);
|
|
253
254
|
const publish = () => {
|
|
254
255
|
if (generation !== this.lifecycleGeneration || this.state !== next)
|
|
255
256
|
return;
|
|
@@ -291,13 +292,20 @@ export class SessionClient {
|
|
|
291
292
|
// Warm cold boot may fetch authoritative empty state before it ever had a
|
|
292
293
|
// local projection. Its authenticated holder is history too. Decoding only
|
|
293
294
|
// identifies local history; authority remains the validated REST response.
|
|
294
|
-
const warmHolderEnded = previousState === null &&
|
|
295
|
-
|
|
295
|
+
const warmHolderEnded = previousState === null &&
|
|
296
|
+
origin === 'request' &&
|
|
297
|
+
typeof bearerAccount === 'string' &&
|
|
298
|
+
bearerAccount.length > 0 &&
|
|
296
299
|
this.host.getDeviceCredential()?.deviceId === next.deviceId;
|
|
297
|
-
if (((previousState?.deviceId === next.deviceId && previousState.accounts.length > 0) ||
|
|
298
|
-
|
|
300
|
+
if (((previousState?.deviceId === next.deviceId && previousState.accounts.length > 0) ||
|
|
301
|
+
warmHolderEnded) &&
|
|
302
|
+
next.accounts.length === 0 &&
|
|
303
|
+
pinnedAccountId === null &&
|
|
304
|
+
this.options.onFullExplicitSignOut) {
|
|
299
305
|
try {
|
|
300
|
-
void this.persistFullSignOut()
|
|
306
|
+
void this.persistFullSignOut()
|
|
307
|
+
.then(finishApply)
|
|
308
|
+
.catch((error) => {
|
|
301
309
|
logger.error('[SessionClient] failed to persist received full sign-out', error);
|
|
302
310
|
finishApply();
|
|
303
311
|
});
|
|
@@ -309,7 +317,10 @@ export class SessionClient {
|
|
|
309
317
|
return true;
|
|
310
318
|
}
|
|
311
319
|
if (needsMintBeforeNotify) {
|
|
312
|
-
void transport
|
|
320
|
+
void transport
|
|
321
|
+
.ensureActiveToken(next)
|
|
322
|
+
.then(finishApply)
|
|
323
|
+
.catch((error) => {
|
|
313
324
|
if (generation !== this.lifecycleGeneration)
|
|
314
325
|
return;
|
|
315
326
|
logger.warn('[SessionClient] ensureActiveToken failed — reverting session state', { component: 'SessionClient' }, error);
|
|
@@ -345,10 +356,19 @@ export class SessionClient {
|
|
|
345
356
|
const issues = parsed.success
|
|
346
357
|
? []
|
|
347
358
|
: parsed.error.issues.map((issue) => issue.code === 'invalid_type'
|
|
348
|
-
? {
|
|
359
|
+
? {
|
|
360
|
+
path: issue.path.join('.'),
|
|
361
|
+
code: issue.code,
|
|
362
|
+
expected: issue.expected,
|
|
363
|
+
received: issue.received,
|
|
364
|
+
}
|
|
349
365
|
: { path: issue.path.join('.'), code: issue.code });
|
|
350
366
|
const keys = raw && typeof raw === 'object' ? Object.keys(raw) : [];
|
|
351
|
-
logger.warn('[SessionClient] discarded invalid session sync', {
|
|
367
|
+
logger.warn('[SessionClient] discarded invalid session sync', {
|
|
368
|
+
component: 'SessionClient',
|
|
369
|
+
issues,
|
|
370
|
+
keys,
|
|
371
|
+
});
|
|
352
372
|
return;
|
|
353
373
|
}
|
|
354
374
|
this.commitSync(sync);
|
|
@@ -431,7 +451,9 @@ export class SessionClient {
|
|
|
431
451
|
}
|
|
432
452
|
/** `GET /session/device/directory` → {@link applyDirectory}. No notify. */
|
|
433
453
|
async fetchDirectory() {
|
|
434
|
-
const res = await this.requestDevice('GET', '/session/device/directory', undefined, {
|
|
454
|
+
const res = await this.requestDevice('GET', '/session/device/directory', undefined, {
|
|
455
|
+
cache: false,
|
|
456
|
+
});
|
|
435
457
|
return this.applyDirectory(res);
|
|
436
458
|
}
|
|
437
459
|
/**
|
|
@@ -540,7 +562,9 @@ export class SessionClient {
|
|
|
540
562
|
}
|
|
541
563
|
}
|
|
542
564
|
async bootstrap() {
|
|
543
|
-
const res = await this.requestDevice('GET', '/session/device/state', undefined, {
|
|
565
|
+
const res = await this.requestDevice('GET', '/session/device/state', undefined, {
|
|
566
|
+
cache: false,
|
|
567
|
+
});
|
|
544
568
|
await this.applySync(res);
|
|
545
569
|
}
|
|
546
570
|
/**
|
|
@@ -626,7 +650,9 @@ export class SessionClient {
|
|
|
626
650
|
* {@link signOutPrincipal} for the two that can tell those apart.
|
|
627
651
|
*/
|
|
628
652
|
async signOut(target) {
|
|
629
|
-
const res = await this.requestDevice('POST', '/session/device/signout', target, {
|
|
653
|
+
const res = await this.requestDevice('POST', '/session/device/signout', target, {
|
|
654
|
+
cache: false,
|
|
655
|
+
});
|
|
630
656
|
await this.applySync(res);
|
|
631
657
|
this.postCommitPing();
|
|
632
658
|
if (this.state?.accounts.length === 0)
|
|
@@ -669,14 +695,19 @@ export class SessionClient {
|
|
|
669
695
|
* applied this revision.
|
|
670
696
|
*/
|
|
671
697
|
async removeFromDevice(target) {
|
|
672
|
-
const res = await this.requestDevice('POST', '/session/device/signout', target, {
|
|
698
|
+
const res = await this.requestDevice('POST', '/session/device/signout', target, {
|
|
699
|
+
cache: false,
|
|
700
|
+
});
|
|
673
701
|
const removal = safeParseContract(deviceDirectorySyncSchema, res);
|
|
674
702
|
if (!removal) {
|
|
675
703
|
logger.warn('[SessionClient] discarded invalid device removal response');
|
|
676
704
|
return;
|
|
677
705
|
}
|
|
678
706
|
const directoryApplied = this.applyDirectory(removal.directory);
|
|
679
|
-
const stateApplied = this.commitSync({
|
|
707
|
+
const stateApplied = this.commitSync({
|
|
708
|
+
state: removal.state,
|
|
709
|
+
activeToken: removal.activeToken,
|
|
710
|
+
});
|
|
680
711
|
if (this.fullSignOutPersistence?.state === this.state)
|
|
681
712
|
await this.fullSignOutPersistence.pending;
|
|
682
713
|
// `commitSync` publishes whenever the flat state moved. When only the
|
|
@@ -690,7 +721,9 @@ export class SessionClient {
|
|
|
690
721
|
await this.persistFullSignOut();
|
|
691
722
|
}
|
|
692
723
|
async addCurrentAccount() {
|
|
693
|
-
const res = await this.requestDevice('POST', '/session/device/add', undefined, {
|
|
724
|
+
const res = await this.requestDevice('POST', '/session/device/add', undefined, {
|
|
725
|
+
cache: false,
|
|
726
|
+
});
|
|
694
727
|
await this.applySync(res);
|
|
695
728
|
this.postCommitPing();
|
|
696
729
|
}
|
|
@@ -784,7 +817,9 @@ export class SessionClient {
|
|
|
784
817
|
// if THAT yields nothing — only when no factory was injected.
|
|
785
818
|
const io = this.options.socketFactory ?? (await getSocketIO());
|
|
786
819
|
if (!io) {
|
|
787
|
-
logger.warn('[SessionClient] no socket.io-client; running REST-only (no realtime sync)', {
|
|
820
|
+
logger.warn('[SessionClient] no socket.io-client; running REST-only (no realtime sync)', {
|
|
821
|
+
component: 'SessionClient',
|
|
822
|
+
});
|
|
788
823
|
return;
|
|
789
824
|
}
|
|
790
825
|
if (!this.started)
|
|
@@ -869,7 +904,9 @@ export class SessionClient {
|
|
|
869
904
|
onSessionAccountsChanged(payload) {
|
|
870
905
|
const event = safeParseContract(sessionAccountsChangedEventSchema, payload);
|
|
871
906
|
if (!event) {
|
|
872
|
-
logger.warn('[SessionClient] discarded invalid session_accounts_changed', {
|
|
907
|
+
logger.warn('[SessionClient] discarded invalid session_accounts_changed', {
|
|
908
|
+
component: 'SessionClient',
|
|
909
|
+
});
|
|
873
910
|
return;
|
|
874
911
|
}
|
|
875
912
|
// The socket is in `user:<activeUserId>` for the planted bearer, so this should
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* (or the structural subset below), so the SDK and every agent runtime read the
|
|
14
14
|
* API's already-resolved relationship and effective permissions identically.
|
|
15
15
|
*/
|
|
16
|
-
import { isDelegatedActAsEligibleKind } from
|
|
16
|
+
import { isDelegatedActAsEligibleKind } from '@oxy.so/contracts';
|
|
17
17
|
/**
|
|
18
18
|
* Resolve standing and delegated act-as authority from one account-graph node.
|
|
19
19
|
*
|
|
@@ -28,18 +28,16 @@ import { isDelegatedActAsEligibleKind } from "@oxy.so/contracts";
|
|
|
28
28
|
*/
|
|
29
29
|
export function resolveAccountDelegationAccess(node) {
|
|
30
30
|
const membership = node.callerMembership;
|
|
31
|
-
const activeMembership = membership?.status ===
|
|
32
|
-
const hasStanding = node.relationship ===
|
|
33
|
-
node.relationship === "owner" ||
|
|
34
|
-
activeMembership;
|
|
31
|
+
const activeMembership = membership?.status === 'active';
|
|
32
|
+
const hasStanding = node.relationship === 'self' || node.relationship === 'owner' || activeMembership;
|
|
35
33
|
if (!hasStanding || !isDelegatedActAsEligibleKind(node.kind)) {
|
|
36
34
|
return { hasStanding, canActAs: false };
|
|
37
35
|
}
|
|
38
36
|
if (membership !== null && membership !== undefined) {
|
|
39
37
|
return {
|
|
40
38
|
hasStanding,
|
|
41
|
-
canActAs: activeMembership && membership.permissions.includes(
|
|
39
|
+
canActAs: activeMembership && membership.permissions.includes('account:act_as'),
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
|
-
return { hasStanding, canActAs: node.relationship ===
|
|
42
|
+
return { hasStanding, canActAs: node.relationship === 'owner' };
|
|
45
43
|
}
|
|
@@ -980,7 +980,7 @@ export class AccountDialogController {
|
|
|
980
980
|
// OAuth-bound sessions mint no sessionId on approval — they finalize
|
|
981
981
|
// into an authorization code. The account dialog only starts device
|
|
982
982
|
// sign-in today; stop rather than poll until expiry.
|
|
983
|
-
this.failSignIn('unsupported-flow',
|
|
983
|
+
this.failSignIn('unsupported-flow', "This sign-in flow cannot be completed here. Use the app's OAuth sign-in instead.");
|
|
984
984
|
return;
|
|
985
985
|
}
|
|
986
986
|
if (status.authorized && status.sessionId) {
|
|
@@ -20,9 +20,12 @@ import { logger } from '../logger/index.js';
|
|
|
20
20
|
export function matchesAuthState(actual, expected) {
|
|
21
21
|
if (!actual || !expected)
|
|
22
22
|
return actual === expected;
|
|
23
|
-
return actual.deviceId === expected.deviceId &&
|
|
24
|
-
actual.
|
|
25
|
-
actual.
|
|
23
|
+
return (actual.deviceId === expected.deviceId &&
|
|
24
|
+
actual.deviceSecret === expected.deviceSecret &&
|
|
25
|
+
actual.sessionId === expected.sessionId &&
|
|
26
|
+
actual.userId === expected.userId &&
|
|
27
|
+
actual.accessToken === expected.accessToken &&
|
|
28
|
+
actual.expiresAt === expected.expiresAt);
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* Versioned DURABLE storage key. Holds ONLY the small, re-mint-critical fields
|
|
@@ -350,7 +353,8 @@ export function createNativeAuthStateStore(storage) {
|
|
|
350
353
|
if (sessionMirror !== undefined)
|
|
351
354
|
return sessionMirror;
|
|
352
355
|
const [durable, warm] = await Promise.all([
|
|
353
|
-
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
356
|
+
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
357
|
+
storage.getItem(AUTH_STATE_TOKEN_STORAGE_KEY),
|
|
354
358
|
]);
|
|
355
359
|
return composeState(durable, warm);
|
|
356
360
|
};
|
|
@@ -409,7 +413,8 @@ export function createNativeAuthStateStore(storage) {
|
|
|
409
413
|
if (current === undefined) {
|
|
410
414
|
try {
|
|
411
415
|
const [durable, warm] = await Promise.all([
|
|
412
|
-
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
416
|
+
storage.getItem(AUTH_STATE_STORAGE_KEY),
|
|
417
|
+
storage.getItem(AUTH_STATE_TOKEN_STORAGE_KEY),
|
|
413
418
|
]);
|
|
414
419
|
current = composeState(durable, warm);
|
|
415
420
|
}
|
|
@@ -419,10 +424,14 @@ export function createNativeAuthStateStore(storage) {
|
|
|
419
424
|
}
|
|
420
425
|
}
|
|
421
426
|
const expected = guard.expectedState;
|
|
422
|
-
if (!guard.isCurrent() ||
|
|
423
|
-
current
|
|
424
|
-
current.
|
|
425
|
-
current.
|
|
427
|
+
if (!guard.isCurrent() ||
|
|
428
|
+
!current ||
|
|
429
|
+
current.deviceId !== expected.deviceId ||
|
|
430
|
+
current.deviceSecret !== expected.deviceSecret ||
|
|
431
|
+
current.sessionId !== expected.sessionId ||
|
|
432
|
+
current.userId !== expected.userId ||
|
|
433
|
+
current.accessToken !== expected.accessToken ||
|
|
434
|
+
current.expiresAt !== expected.expiresAt)
|
|
426
435
|
return false;
|
|
427
436
|
}
|
|
428
437
|
suppressedMirror = suppressed;
|
|
@@ -167,7 +167,9 @@ export function createWebIdentityPinStore() {
|
|
|
167
167
|
save: async (pin) => {
|
|
168
168
|
const json = serialize(pin);
|
|
169
169
|
if (!json) {
|
|
170
|
-
logger.error('[identityPin] refusing to persist a malformed identity pin', undefined, {
|
|
170
|
+
logger.error('[identityPin] refusing to persist a malformed identity pin', undefined, {
|
|
171
|
+
component: 'identityPin',
|
|
172
|
+
});
|
|
171
173
|
return false;
|
|
172
174
|
}
|
|
173
175
|
sessionMirror = { publicKey: pin.publicKey.toLowerCase(), accountId: pin.accountId };
|
|
@@ -217,7 +219,9 @@ export function createNativeIdentityPinStore(storage) {
|
|
|
217
219
|
save: async (pin) => {
|
|
218
220
|
const json = serialize(pin);
|
|
219
221
|
if (!json) {
|
|
220
|
-
logger.error('[identityPin] refusing to persist a malformed identity pin', undefined, {
|
|
222
|
+
logger.error('[identityPin] refusing to persist a malformed identity pin', undefined, {
|
|
223
|
+
component: 'identityPin',
|
|
224
|
+
});
|
|
221
225
|
return false;
|
|
222
226
|
}
|
|
223
227
|
sessionMirror = { publicKey: pin.publicKey.toLowerCase(), accountId: pin.accountId };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { matchesAuthState } from './authStateStore.js';
|
|
2
|
-
import { establishIdentitySession, resolveIdentityPin } from './identitySession.js';
|
|
2
|
+
import { establishIdentitySession, resolveIdentityPin, } from './identitySession.js';
|
|
3
3
|
import { isNative } from '../utils/platform.js';
|
|
4
4
|
import { extractErrorStatus } from '../utils/errorUtils.js';
|
|
5
5
|
import { logger } from '../logger/index.js';
|
|
@@ -127,9 +127,12 @@ export async function refreshDeviceSecretArm(deps) {
|
|
|
127
127
|
// push. Its exact live holder's authoritative empty-device rejection
|
|
128
128
|
// must suppress automatic key recovery on this and future boots.
|
|
129
129
|
// A late rejection for a replaced session/local epoch is not current.
|
|
130
|
-
if (pin === null &&
|
|
130
|
+
if (pin === null &&
|
|
131
|
+
store.setAutomaticIdentitySignInSuppressed &&
|
|
132
|
+
(!invalidSecret || (persisted.sessionId && persisted.userId))) {
|
|
131
133
|
const current = await store.load();
|
|
132
|
-
if (!isCurrent() ||
|
|
134
|
+
if (!isCurrent() ||
|
|
135
|
+
oxy.http.getSessionEpoch() !== epoch ||
|
|
133
136
|
current?.deviceId !== persisted.deviceId ||
|
|
134
137
|
current.deviceSecret !== persisted.deviceSecret ||
|
|
135
138
|
current.sessionId !== persisted.sessionId ||
|
|
@@ -137,7 +140,8 @@ export async function refreshDeviceSecretArm(deps) {
|
|
|
137
140
|
current.accessToken !== persisted.accessToken)
|
|
138
141
|
return { status: 'session-ended' };
|
|
139
142
|
if (!(await store.setAutomaticIdentitySignInSuppressed(true, {
|
|
140
|
-
expectedState: persisted,
|
|
143
|
+
expectedState: persisted,
|
|
144
|
+
isCurrent: () => isCurrent() && oxy.http.getSessionEpoch() === epoch,
|
|
141
145
|
})))
|
|
142
146
|
return { status: 'session-ended' };
|
|
143
147
|
}
|
|
@@ -185,7 +189,10 @@ export async function refreshDeviceSecretArm(deps) {
|
|
|
185
189
|
// Persist nextDeviceSecret (read-back-verified) BEFORE planting the token.
|
|
186
190
|
// A failed durable persist must NOT plant.
|
|
187
191
|
const persistedOk = store.saveIfCurrent
|
|
188
|
-
? await store.saveIfCurrent(next, {
|
|
192
|
+
? await store.saveIfCurrent(next, {
|
|
193
|
+
expectedState: persisted,
|
|
194
|
+
isCurrent: () => isCurrent() && oxy.http.getSessionEpoch() === epoch,
|
|
195
|
+
})
|
|
189
196
|
: await store.save(next);
|
|
190
197
|
if (!persistedOk) {
|
|
191
198
|
return { status: 'persist-failed' };
|
|
@@ -244,22 +251,39 @@ export async function refreshSharedDeviceArm(deps) {
|
|
|
244
251
|
}
|
|
245
252
|
return { status: 'transient' };
|
|
246
253
|
}
|
|
247
|
-
const bound = mint.state.accounts.find(account => account.accountId === mint.state.activeAccountId);
|
|
248
|
-
if (!bound ||
|
|
254
|
+
const bound = mint.state.accounts.find((account) => account.accountId === mint.state.activeAccountId);
|
|
255
|
+
if (!bound ||
|
|
256
|
+
mint.state.deviceId !== credential.deviceId ||
|
|
257
|
+
!mint.accessToken ||
|
|
258
|
+
!mint.nextDeviceSecret)
|
|
249
259
|
return { status: 'transient' };
|
|
250
|
-
if (!isCurrent() ||
|
|
260
|
+
if (!isCurrent() ||
|
|
261
|
+
oxy.http.getSessionEpoch() !== epoch ||
|
|
262
|
+
!matchesAuthState(await store.load(), before))
|
|
251
263
|
return { status: 'session-ended' };
|
|
252
264
|
const next = {
|
|
253
|
-
deviceId: mint.state.deviceId,
|
|
254
|
-
|
|
255
|
-
|
|
265
|
+
deviceId: mint.state.deviceId,
|
|
266
|
+
deviceSecret: mint.nextDeviceSecret,
|
|
267
|
+
sessionId: bound.sessionId,
|
|
268
|
+
userId: bound.accountId,
|
|
269
|
+
accessToken: mint.accessToken,
|
|
270
|
+
expiresAt: mint.expiresAt,
|
|
256
271
|
};
|
|
257
|
-
if (!(await store.saveIfCurrent(next, {
|
|
272
|
+
if (!(await store.saveIfCurrent(next, {
|
|
273
|
+
expectedState: before,
|
|
274
|
+
isCurrent: () => isCurrent() && oxy.http.getSessionEpoch() === epoch,
|
|
275
|
+
})))
|
|
258
276
|
return { status: 'persist-failed' };
|
|
259
277
|
if (!isCurrent() || oxy.http.getSessionEpoch() !== epoch)
|
|
260
278
|
return { status: 'session-ended' };
|
|
261
279
|
oxy.session.setAccessToken(mint.accessToken);
|
|
262
|
-
return {
|
|
280
|
+
return {
|
|
281
|
+
status: 'ok',
|
|
282
|
+
token: mint.accessToken,
|
|
283
|
+
sessionId: bound.sessionId,
|
|
284
|
+
userId: bound.accountId,
|
|
285
|
+
state: mint.state,
|
|
286
|
+
};
|
|
263
287
|
});
|
|
264
288
|
}
|
|
265
289
|
/**
|
|
@@ -293,7 +317,10 @@ export async function refreshPersistedSession(deps) {
|
|
|
293
317
|
return null;
|
|
294
318
|
const identity = deps.identity ?? null;
|
|
295
319
|
// The shared identity is never an identity-bound client's recovery path.
|
|
296
|
-
const allowCommonsIdentityFallback = identity
|
|
320
|
+
const allowCommonsIdentityFallback = identity
|
|
321
|
+
? false
|
|
322
|
+
: (deps.allowCommonsIdentityFallback ?? isNative()) &&
|
|
323
|
+
!(await store.isAutomaticIdentitySignInSuppressed?.());
|
|
297
324
|
const epoch = oxy.http.getSessionEpoch();
|
|
298
325
|
// Resolved per call: a re-established identity session can move the pin, and a
|
|
299
326
|
// replaced/removed local key clears it (in which case arm 1 must NOT mint —
|
|
@@ -309,7 +336,10 @@ export async function refreshPersistedSession(deps) {
|
|
|
309
336
|
case 'session-ended':
|
|
310
337
|
return null;
|
|
311
338
|
case 'transient':
|
|
312
|
-
logger.debug('Persisted deviceSecret mint failed (transient) — keeping store', {
|
|
339
|
+
logger.debug('Persisted deviceSecret mint failed (transient) — keeping store', {
|
|
340
|
+
component: 'refresh',
|
|
341
|
+
method: 'refreshPersistedSession',
|
|
342
|
+
});
|
|
313
343
|
return null;
|
|
314
344
|
case 'persist-failed':
|
|
315
345
|
// The server rotated the secret but it did not durably persist. Do NOT fall
|
|
@@ -321,13 +351,21 @@ export async function refreshPersistedSession(deps) {
|
|
|
321
351
|
case 'invalid-secret':
|
|
322
352
|
case 'no-session': {
|
|
323
353
|
if (!identity && deps.sharedDeviceCredential) {
|
|
324
|
-
const joined = await refreshSharedDeviceArm({
|
|
354
|
+
const joined = await refreshSharedDeviceArm({
|
|
355
|
+
oxy,
|
|
356
|
+
store,
|
|
357
|
+
shared: deps.sharedDeviceCredential,
|
|
358
|
+
rejectedLocalHolder: true,
|
|
359
|
+
isCurrent: deps.isCurrent,
|
|
360
|
+
});
|
|
325
361
|
if (joined.status === 'ok')
|
|
326
362
|
return joined.token;
|
|
327
363
|
if (joined.status === 'session-ended' || joined.status === 'persist-failed')
|
|
328
364
|
return null;
|
|
329
365
|
}
|
|
330
|
-
if (!identity &&
|
|
366
|
+
if (!identity &&
|
|
367
|
+
arm1.status === 'invalid-secret' &&
|
|
368
|
+
(await store.isAutomaticIdentitySignInSuppressed?.()))
|
|
331
369
|
return null;
|
|
332
370
|
if (arm1.status === 'no-session' && !identity) {
|
|
333
371
|
if (store.setAutomaticIdentitySignInSuppressed)
|
|
@@ -337,7 +375,12 @@ export async function refreshPersistedSession(deps) {
|
|
|
337
375
|
// to turn this signed-out verdict into a new account session.
|
|
338
376
|
const persisted = await store.load();
|
|
339
377
|
if (persisted?.deviceId && persisted.deviceSecret) {
|
|
340
|
-
await store.save({
|
|
378
|
+
await store.save({
|
|
379
|
+
sessionId: '',
|
|
380
|
+
userId: '',
|
|
381
|
+
deviceId: persisted.deviceId,
|
|
382
|
+
deviceSecret: persisted.deviceSecret,
|
|
383
|
+
});
|
|
341
384
|
}
|
|
342
385
|
return null;
|
|
343
386
|
}
|
|
@@ -364,10 +407,17 @@ export async function refreshPersistedSession(deps) {
|
|
|
364
407
|
break;
|
|
365
408
|
case 'no-secret':
|
|
366
409
|
if (!identity && deps.sharedDeviceCredential) {
|
|
367
|
-
const joined = await refreshSharedDeviceArm({
|
|
410
|
+
const joined = await refreshSharedDeviceArm({
|
|
411
|
+
oxy,
|
|
412
|
+
store,
|
|
413
|
+
shared: deps.sharedDeviceCredential,
|
|
414
|
+
isCurrent: deps.isCurrent,
|
|
415
|
+
});
|
|
368
416
|
if (joined.status === 'ok')
|
|
369
417
|
return joined.token;
|
|
370
|
-
if (joined.status === 'session-ended' ||
|
|
418
|
+
if (joined.status === 'session-ended' ||
|
|
419
|
+
joined.status === 'persist-failed' ||
|
|
420
|
+
joined.status === 'transient')
|
|
371
421
|
return null;
|
|
372
422
|
}
|
|
373
423
|
break;
|
|
@@ -377,14 +427,19 @@ export async function refreshPersistedSession(deps) {
|
|
|
377
427
|
}
|
|
378
428
|
// Never after a sign-out: the shared identity is a KEY, not a session, and
|
|
379
429
|
// proving it here would sign the user straight back in.
|
|
380
|
-
if (allowCommonsIdentityFallback &&
|
|
430
|
+
if (allowCommonsIdentityFallback &&
|
|
431
|
+
(deps.isCurrent?.() ?? true) &&
|
|
432
|
+
!oxy.http.hasSessionEnded() &&
|
|
433
|
+
!(await store.isAutomaticIdentitySignInSuppressed?.())) {
|
|
381
434
|
try {
|
|
382
435
|
// Planted here, not by the sign-in: a sign-out that lands while the
|
|
383
436
|
// challenge round-trips must win, or the identity proof signs the user
|
|
384
437
|
// straight back in.
|
|
385
438
|
const session = await oxy.auth.signInWithCommonsIdentity({ plantTokens: false });
|
|
386
439
|
if (session?.accessToken) {
|
|
387
|
-
if (!(deps.isCurrent?.() ?? true) ||
|
|
440
|
+
if (!(deps.isCurrent?.() ?? true) ||
|
|
441
|
+
oxy.http.getSessionEpoch() !== epoch ||
|
|
442
|
+
(await store.isAutomaticIdentitySignInSuppressed?.())) {
|
|
388
443
|
return null;
|
|
389
444
|
}
|
|
390
445
|
// Repopulate the fast device-secret lane from the identity re-mint.
|
|
@@ -398,7 +453,9 @@ export async function refreshPersistedSession(deps) {
|
|
|
398
453
|
expiresAt: session.expiresAt,
|
|
399
454
|
});
|
|
400
455
|
}
|
|
401
|
-
if (!(deps.isCurrent?.() ?? true) ||
|
|
456
|
+
if (!(deps.isCurrent?.() ?? true) ||
|
|
457
|
+
oxy.http.getSessionEpoch() !== epoch ||
|
|
458
|
+
(await store.isAutomaticIdentitySignInSuppressed?.())) {
|
|
402
459
|
return null;
|
|
403
460
|
}
|
|
404
461
|
oxy.session.setAccessToken(session.accessToken);
|
|
@@ -519,7 +576,8 @@ export function startTokenRefreshScheduler(oxy) {
|
|
|
519
576
|
scheduleFromExpiry();
|
|
520
577
|
return;
|
|
521
578
|
}
|
|
522
|
-
void oxy.http
|
|
579
|
+
void oxy.http
|
|
580
|
+
.refreshAccessToken('preflight')
|
|
523
581
|
.then((token) => Boolean(token))
|
|
524
582
|
.catch(() => false)
|
|
525
583
|
.then((ok) => {
|