@oxyhq/core 5.1.1 → 5.2.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/AuthManager.js +27 -5
- package/dist/cjs/OxyServices.base.js +71 -0
- package/dist/cjs/i18n/locales/ar-SA.json +1 -0
- package/dist/cjs/i18n/locales/ca-ES.json +1 -0
- package/dist/cjs/i18n/locales/de-DE.json +1 -0
- package/dist/cjs/i18n/locales/en-US.json +1 -0
- package/dist/cjs/i18n/locales/es-ES.json +1 -0
- package/dist/cjs/i18n/locales/fr-FR.json +1 -0
- package/dist/cjs/i18n/locales/it-IT.json +1 -0
- package/dist/cjs/i18n/locales/ja-JP.json +1 -0
- package/dist/cjs/i18n/locales/ko-KR.json +1 -0
- package/dist/cjs/i18n/locales/locales/ar-SA.json +1 -0
- package/dist/cjs/i18n/locales/locales/ca-ES.json +1 -0
- package/dist/cjs/i18n/locales/locales/de-DE.json +1 -0
- package/dist/cjs/i18n/locales/locales/en-US.json +1 -0
- package/dist/cjs/i18n/locales/locales/es-ES.json +1 -0
- package/dist/cjs/i18n/locales/locales/fr-FR.json +1 -0
- package/dist/cjs/i18n/locales/locales/it-IT.json +1 -0
- package/dist/cjs/i18n/locales/locales/ja-JP.json +1 -0
- package/dist/cjs/i18n/locales/locales/ko-KR.json +1 -0
- package/dist/cjs/i18n/locales/locales/pt-PT.json +1 -0
- package/dist/cjs/i18n/locales/locales/zh-CN.json +1 -0
- package/dist/cjs/i18n/locales/pt-PT.json +1 -0
- package/dist/cjs/i18n/locales/zh-CN.json +1 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/mixins/OxyServices.accounts.js +10 -25
- package/dist/cjs/mixins/OxyServices.assets.js +76 -0
- package/dist/cjs/utils/cache.js +2 -0
- package/dist/cjs/utils/ssoBounce.js +59 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +27 -5
- package/dist/esm/OxyServices.base.js +71 -0
- package/dist/esm/i18n/locales/ar-SA.json +1 -0
- package/dist/esm/i18n/locales/ca-ES.json +1 -0
- package/dist/esm/i18n/locales/de-DE.json +1 -0
- package/dist/esm/i18n/locales/en-US.json +1 -0
- package/dist/esm/i18n/locales/es-ES.json +1 -0
- package/dist/esm/i18n/locales/fr-FR.json +1 -0
- package/dist/esm/i18n/locales/it-IT.json +1 -0
- package/dist/esm/i18n/locales/ja-JP.json +1 -0
- package/dist/esm/i18n/locales/ko-KR.json +1 -0
- package/dist/esm/i18n/locales/locales/ar-SA.json +1 -0
- package/dist/esm/i18n/locales/locales/ca-ES.json +1 -0
- package/dist/esm/i18n/locales/locales/de-DE.json +1 -0
- package/dist/esm/i18n/locales/locales/en-US.json +1 -0
- package/dist/esm/i18n/locales/locales/es-ES.json +1 -0
- package/dist/esm/i18n/locales/locales/fr-FR.json +1 -0
- package/dist/esm/i18n/locales/locales/it-IT.json +1 -0
- package/dist/esm/i18n/locales/locales/ja-JP.json +1 -0
- package/dist/esm/i18n/locales/locales/ko-KR.json +1 -0
- package/dist/esm/i18n/locales/locales/pt-PT.json +1 -0
- package/dist/esm/i18n/locales/locales/zh-CN.json +1 -0
- package/dist/esm/i18n/locales/pt-PT.json +1 -0
- package/dist/esm/i18n/locales/zh-CN.json +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/mixins/OxyServices.accounts.js +10 -25
- package/dist/esm/mixins/OxyServices.assets.js +76 -0
- package/dist/esm/utils/cache.js +2 -0
- package/dist/esm/utils/ssoBounce.js +57 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.base.d.ts +38 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/mixins/OxyServices.accounts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.appData.d.ts +1 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +36 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +1 -0
- package/dist/types/mixins/OxyServices.civic.d.ts +1 -0
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +1 -0
- package/dist/types/mixins/OxyServices.contacts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
- package/dist/types/mixins/OxyServices.features.d.ts +1 -0
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
- package/dist/types/mixins/OxyServices.identity.d.ts +1 -0
- package/dist/types/mixins/OxyServices.language.d.ts +1 -0
- package/dist/types/mixins/OxyServices.links.d.ts +1 -0
- package/dist/types/mixins/OxyServices.location.d.ts +1 -0
- package/dist/types/mixins/OxyServices.nodes.d.ts +1 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
- package/dist/types/mixins/OxyServices.reputation.d.ts +1 -0
- package/dist/types/mixins/OxyServices.security.d.ts +1 -0
- package/dist/types/mixins/OxyServices.silent.d.ts +1 -0
- package/dist/types/mixins/OxyServices.sso.d.ts +1 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.user.d.ts +1 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
- package/dist/types/models/interfaces.d.ts +23 -0
- package/dist/types/utils/ssoBounce.d.ts +47 -0
- package/package.json +1 -1
- package/src/AuthManager.ts +29 -5
- package/src/OxyServices.base.ts +79 -0
- package/src/__tests__/establishDeviceRefreshSlot.test.ts +221 -0
- package/src/i18n/locales/ar-SA.json +1 -0
- package/src/i18n/locales/ca-ES.json +1 -0
- package/src/i18n/locales/de-DE.json +1 -0
- package/src/i18n/locales/en-US.json +1 -0
- package/src/i18n/locales/es-ES.json +1 -0
- package/src/i18n/locales/fr-FR.json +1 -0
- package/src/i18n/locales/it-IT.json +1 -0
- package/src/i18n/locales/ja-JP.json +1 -0
- package/src/i18n/locales/ko-KR.json +1 -0
- package/src/i18n/locales/pt-PT.json +1 -0
- package/src/i18n/locales/zh-CN.json +1 -0
- package/src/index.ts +3 -0
- package/src/mixins/OxyServices.accounts.ts +10 -33
- package/src/mixins/OxyServices.assets.ts +92 -1
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +135 -0
- package/src/models/interfaces.ts +24 -0
- package/src/utils/__tests__/ssoBounce.test.ts +41 -0
- package/src/utils/cache.ts +2 -0
- package/src/utils/ssoBounce.ts +61 -0
package/src/models/interfaces.ts
CHANGED
|
@@ -533,6 +533,30 @@ export interface ServiceAssetMetadata {
|
|
|
533
533
|
status: 'active' | 'trash';
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Reverse-lookup asset metadata returned by `POST /assets/service/by-sha256`.
|
|
538
|
+
*
|
|
539
|
+
* Resolves a content-addressed `sha256` digest back to the live Oxy asset that
|
|
540
|
+
* holds those bytes: its file `id`, MIME type, byte `size`, storage `status`,
|
|
541
|
+
* and — for active, public, CDN-reachable assets only — a public `url`
|
|
542
|
+
* (`cloud.oxy.so`). This is the inverse of {@link ServiceAssetMetadata}: it lets
|
|
543
|
+
* a `files:read`-scoped service-to-server caller (e.g. Mention's MTN materializer
|
|
544
|
+
* / node-blob sync) turn a record's `blob.sha256` into a servable asset.
|
|
545
|
+
*
|
|
546
|
+
* `url` is omitted for private/unlisted assets (and for public assets whose
|
|
547
|
+
* bytes are not yet CDN-reachable) — those must be streamed through the origin.
|
|
548
|
+
* Unknown or deleted hashes are omitted from the response (never error the whole
|
|
549
|
+
* batch), so the result may be shorter than the requested hash list.
|
|
550
|
+
*/
|
|
551
|
+
export interface ServiceAssetMetadataBySha {
|
|
552
|
+
sha256: string;
|
|
553
|
+
id: string;
|
|
554
|
+
mime: string;
|
|
555
|
+
size: number;
|
|
556
|
+
status: 'active' | 'trash';
|
|
557
|
+
url?: string;
|
|
558
|
+
}
|
|
559
|
+
|
|
536
560
|
/**
|
|
537
561
|
* Account storage usage (server-side usage, not local AsyncStorage)
|
|
538
562
|
*/
|
|
@@ -16,9 +16,11 @@ import {
|
|
|
16
16
|
ssoNoSessionKey,
|
|
17
17
|
ssoAttemptedKey,
|
|
18
18
|
ssoPriorSessionKey,
|
|
19
|
+
ssoSignedOutKey,
|
|
19
20
|
buildSsoBounceUrl,
|
|
20
21
|
isCentralIdPOrigin,
|
|
21
22
|
guardActive,
|
|
23
|
+
silentRestoreSuppressed,
|
|
22
24
|
allowSsoBounce,
|
|
23
25
|
} from '../ssoBounce';
|
|
24
26
|
import { CENTRAL_AUTH_URL } from '../authWebUrl';
|
|
@@ -40,11 +42,50 @@ describe('per-origin key builders', () => {
|
|
|
40
42
|
expect(ssoNoSessionKey(origin)).toBe('oxy_sso_no_session:https://mention.earth');
|
|
41
43
|
expect(ssoAttemptedKey(origin)).toBe('oxy_sso_attempted:https://mention.earth');
|
|
42
44
|
expect(ssoPriorSessionKey(origin)).toBe('oxy_sso_prior_session:https://mention.earth');
|
|
45
|
+
expect(ssoSignedOutKey(origin)).toBe('oxy_signed_out:https://mention.earth');
|
|
43
46
|
});
|
|
44
47
|
|
|
45
48
|
it('namespaces keys per origin so two RPs never collide', () => {
|
|
46
49
|
expect(ssoStateKey('https://a.test')).not.toBe(ssoStateKey('https://b.test'));
|
|
47
50
|
expect(ssoPriorSessionKey('https://a.test')).not.toBe(ssoPriorSessionKey('https://b.test'));
|
|
51
|
+
expect(ssoSignedOutKey('https://a.test')).not.toBe(ssoSignedOutKey('https://b.test'));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('silentRestoreSuppressed (deliberately-signed-out gate)', () => {
|
|
56
|
+
const origin = 'https://accounts.oxy.so';
|
|
57
|
+
|
|
58
|
+
function storageWith(value: string | null): Pick<Storage, 'getItem'> {
|
|
59
|
+
return { getItem: (key: string) => (key === ssoSignedOutKey(origin) ? value : null) };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
it('is SUPPRESSED when the signed-out flag is set to "1" for this origin', () => {
|
|
63
|
+
expect(silentRestoreSuppressed(storageWith('1'), origin)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('is NOT suppressed when the flag is absent (normal restore)', () => {
|
|
67
|
+
expect(silentRestoreSuppressed(storageWith(null), origin)).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('is NOT suppressed for any non-"1" value (only the exact set value gates)', () => {
|
|
71
|
+
expect(silentRestoreSuppressed(storageWith('0'), origin)).toBe(false);
|
|
72
|
+
expect(silentRestoreSuppressed(storageWith(''), origin)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('is per-origin: another origin\'s flag does not suppress this one', () => {
|
|
76
|
+
const otherOriginFlag: Pick<Storage, 'getItem'> = {
|
|
77
|
+
getItem: (key: string) => (key === ssoSignedOutKey('https://other.test') ? '1' : null),
|
|
78
|
+
};
|
|
79
|
+
expect(silentRestoreSuppressed(otherOriginFlag, origin)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('fails safe (NOT suppressed, never throws) when getItem throws', () => {
|
|
83
|
+
const throwing: Pick<Storage, 'getItem'> = {
|
|
84
|
+
getItem: () => {
|
|
85
|
+
throw new Error('storage locked');
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
expect(silentRestoreSuppressed(throwing, origin)).toBe(false);
|
|
48
89
|
});
|
|
49
90
|
});
|
|
50
91
|
|
package/src/utils/cache.ts
CHANGED
package/src/utils/ssoBounce.ts
CHANGED
|
@@ -69,6 +69,7 @@ const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
|
|
|
69
69
|
const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
|
|
70
70
|
const CALLBACK_BOOTSTRAP_KEY_PREFIX = 'oxy_sso_callback_bootstrap:';
|
|
71
71
|
const PRIOR_SESSION_KEY_PREFIX = 'oxy_sso_prior_session:';
|
|
72
|
+
const SIGNED_OUT_KEY_PREFIX = 'oxy_signed_out:';
|
|
72
73
|
|
|
73
74
|
/** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
|
|
74
75
|
export function ssoStateKey(origin: string): string {
|
|
@@ -125,6 +126,37 @@ export function ssoPriorSessionKey(origin: string): string {
|
|
|
125
126
|
return `${PRIOR_SESSION_KEY_PREFIX}${origin}`;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Per-origin DURABLE "the user DELIBERATELY signed out on this device/origin"
|
|
131
|
+
* flag.
|
|
132
|
+
*
|
|
133
|
+
* Like {@link ssoPriorSessionKey} this lives in DURABLE storage (web
|
|
134
|
+
* `localStorage`; services uses its own `storageKeyPrefix`-scoped key), NOT the
|
|
135
|
+
* per-tab `sessionStorage` the loop-breaker keys use — it must survive a reload.
|
|
136
|
+
*
|
|
137
|
+
* It exists purely to suppress AUTOMATIC silent restore after a deliberate
|
|
138
|
+
* sign-out: a still-live IdP session (the central `fedcm_session` / the FedCM
|
|
139
|
+
* credential association) would otherwise let `fedcm-silent` / the per-apex
|
|
140
|
+
* `/auth/silent` iframe re-mint a session on the very next cold boot, so a user
|
|
141
|
+
* who pressed "Sign out" gets silently signed back in on reload. With this flag
|
|
142
|
+
* set, those silent cold-boot steps are skipped while the Gmail-style
|
|
143
|
+
* returning-account fast-path is otherwise preserved.
|
|
144
|
+
*
|
|
145
|
+
* Lifecycle (mirrors the existing gate machinery — set on a definitive event,
|
|
146
|
+
* cleared on its inverse):
|
|
147
|
+
* - SET on EXPLICIT full sign-out (alongside clearing the prior-session hint
|
|
148
|
+
* and the SSO bounce state).
|
|
149
|
+
* - CLEARED on ANY deliberate sign-in (password, FedCM, account switch, device
|
|
150
|
+
* claim) so a real sign-in fully re-enables silent restore — there is no
|
|
151
|
+
* "stuck signed out" state.
|
|
152
|
+
*
|
|
153
|
+
* NOTE: this gates only AUTOMATIC/silent restore. An INTERACTIVE sign-in always
|
|
154
|
+
* clears it first, so the user can always sign back in.
|
|
155
|
+
*/
|
|
156
|
+
export function ssoSignedOutKey(origin: string): string {
|
|
157
|
+
return `${SIGNED_OUT_KEY_PREFIX}${origin}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
128
160
|
/**
|
|
129
161
|
* Per-origin marker written by the pre-hydration callback bootstrap.
|
|
130
162
|
*
|
|
@@ -267,6 +299,35 @@ export function guardActive(
|
|
|
267
299
|
return now - ts < SSO_GUARD_TTL_MS;
|
|
268
300
|
}
|
|
269
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Whether AUTOMATIC silent restore is SUPPRESSED for this origin because the
|
|
304
|
+
* user deliberately signed out (the durable {@link ssoSignedOutKey} flag).
|
|
305
|
+
*
|
|
306
|
+
* When `true`, the silent cold-boot steps that can re-mint a session from a
|
|
307
|
+
* still-live IdP session WITHOUT user intent — `fedcm-silent` and the per-apex
|
|
308
|
+
* `/auth/silent` iframe — MUST be skipped, so a user who pressed "Sign out" is
|
|
309
|
+
* not silently signed back in on the next reload. Interactive sign-in clears the
|
|
310
|
+
* flag, so this never blocks a deliberate re-sign-in.
|
|
311
|
+
*
|
|
312
|
+
* Defensive: a `getItem` that throws (locked/disabled storage) is treated as NOT
|
|
313
|
+
* suppressed, so the gate fails toward the normal restore behaviour rather than
|
|
314
|
+
* wedging the user out.
|
|
315
|
+
*
|
|
316
|
+
* @param storage - The DURABLE storage to read (web `localStorage`; injected for
|
|
317
|
+
* testability).
|
|
318
|
+
* @param origin - The page origin whose signed-out flag to evaluate.
|
|
319
|
+
*/
|
|
320
|
+
export function silentRestoreSuppressed(
|
|
321
|
+
storage: Pick<Storage, 'getItem'>,
|
|
322
|
+
origin: string,
|
|
323
|
+
): boolean {
|
|
324
|
+
try {
|
|
325
|
+
return storage.getItem(ssoSignedOutKey(origin)) === '1';
|
|
326
|
+
} catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
270
331
|
/**
|
|
271
332
|
* Inputs to the smart {@link allowSsoBounce} gate.
|
|
272
333
|
*/
|