@oxyhq/core 5.5.0 → 7.0.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +7 -4
- package/dist/cjs/OxyServices.base.js +7 -102
- package/dist/cjs/boot/coldBootV2.js +350 -0
- package/dist/cjs/boot/deviceBootReturn.js +152 -0
- package/dist/cjs/crypto/keyManager.js +95 -0
- package/dist/cjs/i18n/locales/en-US.json +13 -1
- package/dist/cjs/i18n/locales/es-ES.json +13 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +13 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +13 -1
- package/dist/cjs/index.js +54 -45
- package/dist/cjs/mixins/OxyServices.accounts.js +13 -26
- package/dist/cjs/mixins/OxyServices.auth.js +66 -201
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +119 -0
- package/dist/cjs/mixins/index.js +8 -17
- package/dist/cjs/server/index.js +2 -2
- package/dist/cjs/session/SessionClient.js +40 -1
- package/dist/cjs/session/authStateStore.js +284 -0
- package/dist/cjs/session/refresh.js +264 -0
- package/dist/cjs/shared/utils/debugUtils.js +3 -3
- package/dist/cjs/utils/accountUtils.js +1 -55
- package/dist/cjs/utils/authWebUrl.js +10 -39
- package/dist/cjs/utils/coldBoot.js +10 -8
- package/dist/cjs/utils/platform.js +19 -0
- package/dist/cjs/utils/registrableApex.js +49 -0
- package/dist/cjs/utils/ssoBounce.js +15 -362
- package/dist/cjs/utils/validationUtils.js +57 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +7 -4
- package/dist/esm/OxyServices.base.js +7 -102
- package/dist/esm/boot/coldBootV2.js +344 -0
- package/dist/esm/boot/deviceBootReturn.js +146 -0
- package/dist/esm/crypto/keyManager.js +95 -0
- package/dist/esm/i18n/locales/en-US.json +13 -1
- package/dist/esm/i18n/locales/es-ES.json +13 -1
- package/dist/esm/i18n/locales/locales/en-US.json +13 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +13 -1
- package/dist/esm/index.js +34 -17
- package/dist/esm/mixins/OxyServices.accounts.js +13 -26
- package/dist/esm/mixins/OxyServices.auth.js +66 -201
- package/dist/esm/mixins/OxyServices.deviceBoot.js +116 -0
- package/dist/esm/mixins/index.js +8 -17
- package/dist/esm/server/index.js +1 -1
- package/dist/esm/session/SessionClient.js +40 -1
- package/dist/esm/session/authStateStore.js +278 -0
- package/dist/esm/session/refresh.js +257 -0
- package/dist/esm/shared/utils/debugUtils.js +3 -3
- package/dist/esm/utils/accountUtils.js +0 -53
- package/dist/esm/utils/authWebUrl.js +9 -37
- package/dist/esm/utils/coldBoot.js +10 -8
- package/dist/esm/utils/platform.js +18 -0
- package/dist/esm/utils/registrableApex.js +46 -0
- package/dist/esm/utils/ssoBounce.js +14 -345
- package/dist/esm/utils/validationUtils.js +56 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +14 -1
- package/dist/types/OxyServices.base.d.ts +0 -52
- package/dist/types/OxyServices.d.ts +0 -25
- package/dist/types/boot/coldBootV2.d.ts +76 -0
- package/dist/types/boot/deviceBootReturn.d.ts +83 -0
- package/dist/types/crypto/keyManager.d.ts +21 -0
- package/dist/types/index.d.ts +14 -21
- package/dist/types/mixins/OxyServices.accounts.d.ts +24 -29
- package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
- package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
- package/dist/types/mixins/OxyServices.auth.d.ts +35 -77
- package/dist/types/mixins/OxyServices.civic.d.ts +0 -2
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
- package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +110 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
- package/dist/types/mixins/OxyServices.features.d.ts +0 -2
- package/dist/types/mixins/OxyServices.identity.d.ts +0 -2
- package/dist/types/mixins/OxyServices.language.d.ts +0 -2
- package/dist/types/mixins/OxyServices.links.d.ts +0 -2
- package/dist/types/mixins/OxyServices.location.d.ts +0 -2
- package/dist/types/mixins/OxyServices.nodes.d.ts +0 -2
- package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
- package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
- package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
- package/dist/types/mixins/OxyServices.security.d.ts +0 -2
- package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.user.d.ts +0 -2
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -2
- package/dist/types/mixins/index.d.ts +5 -9
- package/dist/types/models/interfaces.d.ts +0 -67
- package/dist/types/models/session.d.ts +4 -5
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/session/SessionClient.d.ts +26 -1
- package/dist/types/session/authStateStore.d.ts +119 -0
- package/dist/types/session/refresh.d.ts +93 -0
- package/dist/types/shared/utils/debugUtils.d.ts +3 -3
- package/dist/types/utils/accountUtils.d.ts +2 -17
- package/dist/types/utils/authWebUrl.d.ts +9 -35
- package/dist/types/utils/coldBoot.d.ts +17 -14
- package/dist/types/utils/platform.d.ts +14 -0
- package/dist/types/utils/registrableApex.d.ts +31 -0
- package/dist/types/utils/ssoBounce.d.ts +14 -280
- package/dist/types/utils/validationUtils.d.ts +15 -0
- package/package.json +2 -2
- package/src/HttpService.ts +20 -4
- package/src/OxyServices.base.ts +7 -112
- package/src/OxyServices.ts +0 -38
- package/src/boot/__tests__/coldBootV2.test.ts +317 -0
- package/src/boot/__tests__/deviceBootReturn.test.ts +158 -0
- package/src/boot/coldBootV2.ts +426 -0
- package/src/boot/deviceBootReturn.ts +195 -0
- package/src/crypto/__tests__/sharedDeviceToken.test.ts +24 -0
- package/src/crypto/keyManager.ts +101 -0
- package/src/i18n/locales/en-US.json +13 -1
- package/src/i18n/locales/es-ES.json +13 -1
- package/src/index.ts +75 -65
- package/src/mixins/OxyServices.accounts.ts +27 -39
- package/src/mixins/OxyServices.auth.ts +78 -253
- package/src/mixins/OxyServices.deviceBoot.ts +146 -0
- package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +107 -0
- package/src/mixins/__tests__/accounts.test.ts +17 -44
- package/src/mixins/__tests__/passwordSignIn.test.ts +91 -0
- package/src/mixins/index.ts +10 -23
- package/src/models/interfaces.ts +0 -79
- package/src/models/session.ts +4 -5
- package/src/server/index.ts +1 -1
- package/src/session/SessionClient.ts +53 -2
- package/src/session/__tests__/SessionClient.additive.test.ts +92 -0
- package/src/session/__tests__/SessionClient.rest.test.ts +25 -0
- package/src/session/__tests__/SessionClient.state.test.ts +18 -5
- package/src/session/__tests__/authStateStore.test.ts +209 -0
- package/src/session/__tests__/refresh.test.ts +256 -0
- package/src/session/authStateStore.ts +335 -0
- package/src/session/refresh.ts +334 -0
- package/src/shared/utils/debugUtils.ts +3 -3
- package/src/utils/__tests__/authWebUrl.test.ts +5 -40
- package/src/utils/__tests__/registrableApex.test.ts +62 -0
- package/src/utils/__tests__/validationUtils.test.ts +30 -0
- package/src/utils/accountUtils.ts +2 -65
- package/src/utils/authWebUrl.ts +9 -39
- package/src/utils/coldBoot.ts +17 -14
- package/src/utils/platform.ts +21 -0
- package/src/utils/registrableApex.ts +45 -0
- package/src/utils/ssoBounce.ts +14 -393
- package/src/utils/validationUtils.ts +62 -0
- package/dist/cjs/AuthManager.js +0 -1110
- package/dist/cjs/AuthManagerTypes.js +0 -13
- package/dist/cjs/CrossDomainAuth.js +0 -206
- package/dist/cjs/mixins/OxyServices.fedcm.js +0 -823
- package/dist/cjs/mixins/OxyServices.redirect.js +0 -95
- package/dist/cjs/mixins/OxyServices.silent.js +0 -204
- package/dist/cjs/mixins/OxyServices.sso.js +0 -208
- package/dist/cjs/utils/fapiAutoDetect.js +0 -99
- package/dist/cjs/utils/ssoEstablish.js +0 -110
- package/dist/cjs/utils/ssoReturn.js +0 -275
- package/dist/esm/AuthManager.js +0 -1105
- package/dist/esm/AuthManagerTypes.js +0 -12
- package/dist/esm/CrossDomainAuth.js +0 -201
- package/dist/esm/mixins/OxyServices.fedcm.js +0 -821
- package/dist/esm/mixins/OxyServices.redirect.js +0 -92
- package/dist/esm/mixins/OxyServices.silent.js +0 -202
- package/dist/esm/mixins/OxyServices.sso.js +0 -204
- package/dist/esm/utils/fapiAutoDetect.js +0 -95
- package/dist/esm/utils/ssoEstablish.js +0 -107
- package/dist/esm/utils/ssoReturn.js +0 -271
- package/dist/types/AuthManager.d.ts +0 -380
- package/dist/types/AuthManagerTypes.d.ts +0 -81
- package/dist/types/CrossDomainAuth.d.ts +0 -164
- package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -331
- package/dist/types/mixins/OxyServices.redirect.d.ts +0 -92
- package/dist/types/mixins/OxyServices.silent.d.ts +0 -132
- package/dist/types/mixins/OxyServices.sso.d.ts +0 -138
- package/dist/types/utils/fapiAutoDetect.d.ts +0 -56
- package/dist/types/utils/ssoEstablish.d.ts +0 -85
- package/dist/types/utils/ssoReturn.d.ts +0 -156
- package/src/AuthManager.ts +0 -1269
- package/src/AuthManagerTypes.ts +0 -86
- package/src/CrossDomainAuth.ts +0 -243
- package/src/__tests__/authManager.cookiePath.test.ts +0 -390
- package/src/__tests__/authManager.security.test.ts +0 -377
- package/src/__tests__/crossDomainAuth.test.ts +0 -116
- package/src/__tests__/establishDeviceRefreshSlot.test.ts +0 -221
- package/src/mixins/OxyServices.fedcm.ts +0 -1026
- package/src/mixins/OxyServices.redirect.ts +0 -122
- package/src/mixins/OxyServices.silent.ts +0 -272
- package/src/mixins/OxyServices.sso.ts +0 -261
- package/src/mixins/__tests__/constructorAuthWebUrl.test.ts +0 -85
- package/src/mixins/__tests__/fedcm.test.ts +0 -667
- package/src/mixins/__tests__/sessionBaseUrl.test.ts +0 -61
- package/src/mixins/__tests__/silent.test.ts +0 -102
- package/src/mixins/__tests__/sso.test.ts +0 -228
- package/src/utils/__tests__/consumeSsoReturn.test.ts +0 -816
- package/src/utils/__tests__/fapiAutoDetect.test.ts +0 -183
- package/src/utils/__tests__/ssoBounce.test.ts +0 -219
- package/src/utils/__tests__/ssoEstablish.test.ts +0 -204
- package/src/utils/__tests__/ssoReturn.test.ts +0 -276
- package/src/utils/fapiAutoDetect.ts +0 -91
- package/src/utils/ssoEstablish.ts +0 -174
- package/src/utils/ssoReturn.ts +0 -389
|
@@ -1,50 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Central IdP apex constant.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `resolveCentralAuthUrl` + `CENTRAL_AUTH_URL` were removed in the device-first /
|
|
5
|
+
* legacy-final cutovers. `CENTRAL_IDP_APEX` survives because live server-side
|
|
6
|
+
* callers (the `@oxyhq/core/server` CORS helper + the IdP worker) import it.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
+
import { CENTRAL_IDP_APEX } from '../authWebUrl';
|
|
9
10
|
|
|
10
11
|
describe('CENTRAL_IDP_APEX', () => {
|
|
11
12
|
it('is the central IdP registrable apex', () => {
|
|
12
13
|
expect(CENTRAL_IDP_APEX).toBe('oxy.so');
|
|
13
14
|
});
|
|
14
15
|
});
|
|
15
|
-
|
|
16
|
-
describe('CENTRAL_AUTH_URL', () => {
|
|
17
|
-
it('is the central IdP origin with no trailing slash', () => {
|
|
18
|
-
expect(CENTRAL_AUTH_URL).toBe('https://auth.oxy.so');
|
|
19
|
-
expect(CENTRAL_AUTH_URL.endsWith('/')).toBe(false);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('is derived from CENTRAL_IDP_APEX (apex and origin never drift)', () => {
|
|
23
|
-
expect(CENTRAL_AUTH_URL).toBe(`https://auth.${CENTRAL_IDP_APEX}`);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('resolveCentralAuthUrl', () => {
|
|
28
|
-
it('returns the central default when no explicit value is given', () => {
|
|
29
|
-
expect(resolveCentralAuthUrl()).toBe(CENTRAL_AUTH_URL);
|
|
30
|
-
expect(resolveCentralAuthUrl(undefined)).toBe('https://auth.oxy.so');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('returns the explicit value when provided (explicit wins)', () => {
|
|
34
|
-
expect(resolveCentralAuthUrl('https://auth.mention.earth')).toBe(
|
|
35
|
-
'https://auth.mention.earth',
|
|
36
|
-
);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('does not read any ambient DOM/window state', () => {
|
|
40
|
-
// Even with a window installed, the result is purely a function of the arg.
|
|
41
|
-
(globalThis as unknown as { window: unknown }).window = {
|
|
42
|
-
location: { hostname: 'mention.earth', protocol: 'https:' },
|
|
43
|
-
};
|
|
44
|
-
try {
|
|
45
|
-
expect(resolveCentralAuthUrl()).toBe('https://auth.oxy.so');
|
|
46
|
-
} finally {
|
|
47
|
-
delete (globalThis as Record<string, unknown>).window;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `registrableApex` — the eTLD+1 host kernel (Public Suffix List aware).
|
|
3
|
+
*
|
|
4
|
+
* The client `autoDetectAuthWebUrl` helper was removed in the device-first
|
|
5
|
+
* cutover; only the registrable-apex kernel survives (used by the api SSO
|
|
6
|
+
* surface + the IdP). These cases lock its Public-Suffix-List behaviour.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { registrableApex } from '../registrableApex';
|
|
10
|
+
|
|
11
|
+
describe('registrableApex', () => {
|
|
12
|
+
it('returns the eTLD+1 for a normal two-label host', () => {
|
|
13
|
+
expect(registrableApex('mention.earth')).toBe('mention.earth');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('strips subdomains down to the trailing two labels', () => {
|
|
17
|
+
expect(registrableApex('www.mention.earth')).toBe('mention.earth');
|
|
18
|
+
expect(registrableApex('deep.app.homiio.com')).toBe('homiio.com');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('lower-cases the input', () => {
|
|
22
|
+
expect(registrableApex('WWW.Mention.EARTH')).toBe('mention.earth');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns the eTLD+1 for hosts under multi-part public suffixes', () => {
|
|
26
|
+
expect(registrableApex('foo.co.uk')).toBe('foo.co.uk');
|
|
27
|
+
expect(registrableApex('www.foo.co.uk')).toBe('foo.co.uk');
|
|
28
|
+
expect(registrableApex('shop.victim.com.tr')).toBe('victim.com.tr');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns the eTLD+1 for hosts under private hosted suffixes', () => {
|
|
32
|
+
expect(registrableApex('honest.github.io')).toBe('honest.github.io');
|
|
33
|
+
expect(registrableApex('app.victim.pages.dev')).toBe('victim.pages.dev');
|
|
34
|
+
expect(registrableApex('site.victim.netlify.app')).toBe('victim.netlify.app');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns null for bare public and private suffixes', () => {
|
|
38
|
+
expect(registrableApex('co.uk')).toBeNull();
|
|
39
|
+
expect(registrableApex('github.io')).toBeNull();
|
|
40
|
+
expect(registrableApex('pages.dev')).toBeNull();
|
|
41
|
+
expect(registrableApex('netlify.app')).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns null for IPv4 literals', () => {
|
|
45
|
+
expect(registrableApex('192.168.1.10')).toBeNull();
|
|
46
|
+
expect(registrableApex('10.0.0.1')).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns null for IPv6 literals and hosts carrying a port', () => {
|
|
50
|
+
expect(registrableApex('[::1]')).toBeNull();
|
|
51
|
+
expect(registrableApex('mention.earth:3000')).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns null for single-label hosts', () => {
|
|
55
|
+
expect(registrableApex('intranet')).toBeNull();
|
|
56
|
+
expect(registrableApex('localhost')).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns null for empty input', () => {
|
|
60
|
+
expect(registrableApex('')).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isValidEmail,
|
|
8
8
|
isValidUsername,
|
|
9
9
|
isValidPassword,
|
|
10
|
+
isValidDisplayName,
|
|
10
11
|
isValidUUID,
|
|
11
12
|
isValidDate,
|
|
12
13
|
isValidFileSize,
|
|
@@ -139,6 +140,35 @@ describe('Validation Utils', () => {
|
|
|
139
140
|
});
|
|
140
141
|
});
|
|
141
142
|
|
|
143
|
+
describe('isValidDisplayName', () => {
|
|
144
|
+
it('should return true for clean names (letters, spaces, apostrophe)', () => {
|
|
145
|
+
expect(isValidDisplayName("Renée O'Brien")).toBe(true);
|
|
146
|
+
expect(isValidDisplayName('Ada Lovelace')).toBe(true);
|
|
147
|
+
expect(isValidDisplayName('山田太郎')).toBe(true);
|
|
148
|
+
expect(isValidDisplayName('')).toBe(true); // empty is valid; non-empty enforced elsewhere
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should return false for emoji, symbols, digits, and punctuation', () => {
|
|
152
|
+
expect(isValidDisplayName('nixCraft \u{1f427}')).toBe(false); // penguin emoji
|
|
153
|
+
expect(isValidDisplayName('Agent007')).toBe(false);
|
|
154
|
+
expect(isValidDisplayName('Jean-Luc')).toBe(false);
|
|
155
|
+
expect(isValidDisplayName('J.R.')).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should return false for control whitespace (tab/newline/CR)', () => {
|
|
159
|
+
// \p{Zs} (space separators only) rejects layout-breaking / multi-line
|
|
160
|
+
// spoofing whitespace that \s would have admitted.
|
|
161
|
+
expect(isValidDisplayName('Ada\tLovelace')).toBe(false);
|
|
162
|
+
expect(isValidDisplayName('Ada\nLovelace')).toBe(false);
|
|
163
|
+
expect(isValidDisplayName('Ada\rLovelace')).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should return true for Unicode space separators', () => {
|
|
167
|
+
expect(isValidDisplayName('Ada Lovelace')).toBe(true); // NBSP
|
|
168
|
+
expect(isValidDisplayName('山田 太郎')).toBe(true); // ideographic space
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
142
172
|
describe('isValidUUID', () => {
|
|
143
173
|
it('should return true for valid UUIDs', () => {
|
|
144
174
|
expect(isValidUUID('123e4567-e89b-12d3-a456-426614174000')).toBe(true);
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { translate } from '../i18n';
|
|
7
|
-
import type { RefreshAllAccount } from '../models/interfaces';
|
|
8
7
|
|
|
9
8
|
export interface QuickAccount {
|
|
10
9
|
sessionId: string;
|
|
@@ -15,9 +14,8 @@ export interface QuickAccount {
|
|
|
15
14
|
avatarUrl?: string;
|
|
16
15
|
/**
|
|
17
16
|
* Device-local account slot index, 0..N-1 (Google-style multi-account).
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* remain valid; web flows always populate it after `refreshAllSessions`.
|
|
17
|
+
* Optional so that pre-multi-account QuickAccounts (sessionId-only) remain
|
|
18
|
+
* valid; the device session set populates it where available.
|
|
21
19
|
*/
|
|
22
20
|
authuser?: number;
|
|
23
21
|
/**
|
|
@@ -178,67 +176,6 @@ export const createQuickAccount = (
|
|
|
178
176
|
};
|
|
179
177
|
};
|
|
180
178
|
|
|
181
|
-
/**
|
|
182
|
-
* Merge a fresh `/auth/refresh-all` snapshot into an existing QuickAccount
|
|
183
|
-
* list, preserving any cached fields (`avatarUrl`) for slots that didn't
|
|
184
|
-
* change. The fresh response is canonical: the resulting list contains EXACTLY
|
|
185
|
-
* the slots present in `fresh`, sorted by `authuser` ascending. Stale stored
|
|
186
|
-
* accounts that no longer appear in `fresh` are dropped (the server already
|
|
187
|
-
* authoritatively cleared the corresponding cookie).
|
|
188
|
-
*
|
|
189
|
-
* @param stored Previously persisted QuickAccount list (any order).
|
|
190
|
-
* @param fresh Server's authoritative refresh-all response.
|
|
191
|
-
* @returns Canonical merged list, sorted by `authuser` asc.
|
|
192
|
-
*/
|
|
193
|
-
export const mergeAccountsFromRefreshAll = (
|
|
194
|
-
stored: QuickAccount[] | undefined,
|
|
195
|
-
fresh: RefreshAllAccount[],
|
|
196
|
-
): QuickAccount[] => {
|
|
197
|
-
const storedByAuthuser = new Map<number, QuickAccount>();
|
|
198
|
-
if (stored) {
|
|
199
|
-
for (const account of stored) {
|
|
200
|
-
if (typeof account.authuser === 'number') {
|
|
201
|
-
storedByAuthuser.set(account.authuser, account);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const merged: QuickAccount[] = fresh.map((entry) => {
|
|
207
|
-
const previous = storedByAuthuser.get(entry.authuser);
|
|
208
|
-
// Preserve any previously cached identity for a slot that arrives
|
|
209
|
-
// without a user shape rather than overwriting it with blanks, and let
|
|
210
|
-
// AuthManager's getCurrentUser() hydration refresh it on the next
|
|
211
|
-
// snapshot.
|
|
212
|
-
const wireUser = entry.user;
|
|
213
|
-
const username = wireUser?.username ?? previous?.username ?? '';
|
|
214
|
-
const displayName = getAccountDisplayName({
|
|
215
|
-
name: wireUser?.name,
|
|
216
|
-
username,
|
|
217
|
-
});
|
|
218
|
-
const avatar = wireUser?.avatar ?? previous?.avatar ?? undefined;
|
|
219
|
-
const avatarUrl =
|
|
220
|
-
previous && previous.avatar === avatar ? previous.avatarUrl : undefined;
|
|
221
|
-
return {
|
|
222
|
-
sessionId: entry.sessionId,
|
|
223
|
-
userId: wireUser?.id ?? previous?.userId,
|
|
224
|
-
username,
|
|
225
|
-
displayName,
|
|
226
|
-
avatar,
|
|
227
|
-
avatarUrl,
|
|
228
|
-
authuser: entry.authuser,
|
|
229
|
-
color: wireUser?.color ?? previous?.color ?? null,
|
|
230
|
-
};
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
merged.sort((a, b) => {
|
|
234
|
-
const aIdx = a.authuser ?? Number.POSITIVE_INFINITY;
|
|
235
|
-
const bIdx = b.authuser ?? Number.POSITIVE_INFINITY;
|
|
236
|
-
return aIdx - bIdx;
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
return merged;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
179
|
/**
|
|
243
180
|
* Return the account's preferred Bloom color preset, or `null` if it has no
|
|
244
181
|
* preference. Centralises the `color ?? null` normalisation so consumers can
|
package/src/utils/authWebUrl.ts
CHANGED
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Central IdP
|
|
2
|
+
* Central IdP apex constant.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* This module is intentionally pure: it performs no DOM access, reads no
|
|
12
|
-
* `window`/`location`, and has no side effects. It is the single source of
|
|
13
|
-
* truth for the central IdP origin so call sites never hardcode the literal.
|
|
14
|
-
*
|
|
15
|
-
* Note: this is distinct from `autoDetectAuthWebUrl` (per-apex `auth.<rp-apex>`
|
|
16
|
-
* derivation). The central-SSO path deliberately does NOT auto-detect per-apex
|
|
17
|
-
* IdPs — it is central only. An explicitly-configured `authWebUrl` still wins.
|
|
4
|
+
* The client SSO/FedCM resolvers (`resolveCentralAuthUrl`, `CENTRAL_AUTH_URL`)
|
|
5
|
+
* were removed in the device-first / legacy-final cutovers. The lone survivor is
|
|
6
|
+
* `CENTRAL_IDP_APEX`, kept because it has a LIVE consumer —
|
|
7
|
+
* `@oxyhq/core/server`'s CORS helper (`server/cors.ts`'s `createOxyCors`)
|
|
8
|
+
* auto-allows `*.oxy.so` from it. That CORS use is permanent, so this stays
|
|
9
|
+
* past the SSO/FedCM teardown.
|
|
18
10
|
*/
|
|
19
11
|
|
|
20
12
|
/**
|
|
21
13
|
* The registrable apex (eTLD+1) of the Oxy ecosystem's central Identity
|
|
22
|
-
* Provider
|
|
23
|
-
*
|
|
24
|
-
* regardless of which per-apex `auth.<rp>` host served a given request.
|
|
25
|
-
*
|
|
26
|
-
* Kept as a standalone constant so the IdP worker and the SDK derive the same
|
|
27
|
-
* literal from one source of truth (the worker imports it to brand assertions).
|
|
14
|
+
* Provider, reachable at `auth.${CENTRAL_IDP_APEX}`. Single source of truth so
|
|
15
|
+
* the CORS helper (and anything else that needs the central apex) never drifts.
|
|
28
16
|
*/
|
|
29
17
|
export const CENTRAL_IDP_APEX = 'oxy.so';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The canonical central Identity Provider origin for the Oxy ecosystem.
|
|
33
|
-
* No trailing slash. Derived from {@link CENTRAL_IDP_APEX} so the apex and the
|
|
34
|
-
* full origin never drift apart.
|
|
35
|
-
*/
|
|
36
|
-
export const CENTRAL_AUTH_URL = `https://auth.${CENTRAL_IDP_APEX}`;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Resolve the central IdP origin, honouring an explicit override.
|
|
40
|
-
*
|
|
41
|
-
* @param explicit - A caller-supplied auth web URL, or `undefined`/empty to use
|
|
42
|
-
* the central default. An explicit non-empty value always wins.
|
|
43
|
-
* @returns The explicit value when provided, otherwise {@link CENTRAL_AUTH_URL}.
|
|
44
|
-
*/
|
|
45
|
-
export function resolveCentralAuthUrl(explicit?: string): string {
|
|
46
|
-
return explicit ?? CENTRAL_AUTH_URL;
|
|
47
|
-
}
|
package/src/utils/coldBoot.ts
CHANGED
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
* authentication resolution.
|
|
4
4
|
*
|
|
5
5
|
* On a fresh page load / app launch the SDK may have several ways to recover an
|
|
6
|
-
* existing session (
|
|
7
|
-
*
|
|
8
|
-
* order
|
|
9
|
-
* skipped. This module encodes exactly that contract and
|
|
6
|
+
* existing session (a persisted refresh-token family, a shared-keychain
|
|
7
|
+
* identity, a cross-domain boot-fragment return, ...). They must be attempted
|
|
8
|
+
* in a deterministic order, and the FIRST one that yields a session wins —
|
|
9
|
+
* every later step is skipped. This module encodes exactly that contract and
|
|
10
|
+
* nothing else.
|
|
10
11
|
*
|
|
11
12
|
* Design constraints (all enforced):
|
|
12
13
|
* - PURE: no DOM, no `navigator`, no `window`, no React, no platform globals.
|
|
13
14
|
* - NO module-level mutable state. Every call to {@link runColdBoot} is fully
|
|
14
15
|
* self-contained, so it is safe under bundler re-evaluation (e.g. the Metro
|
|
15
|
-
* web bundle
|
|
16
|
-
*
|
|
17
|
-
* - Architecture-agnostic:
|
|
18
|
-
*
|
|
16
|
+
* web bundle — the reason any run-once guard for a step must live in the
|
|
17
|
+
* calling consumer, never in a core module-level singleton).
|
|
18
|
+
* - Architecture-agnostic: it knows nothing about HOW a step resolves a
|
|
19
|
+
* session; `runSessionColdBoot` (`boot/coldBootV2.ts`) is the current
|
|
20
|
+
* device-first consumer.
|
|
19
21
|
*
|
|
20
22
|
* A step is skipped (without running) when its `enabled` predicate returns
|
|
21
23
|
* false. Any thrown error — from either `enabled` or `run` — is reported via
|
|
@@ -104,15 +106,16 @@ export interface RunColdBootOptions<S> {
|
|
|
104
106
|
* fails to settle before the deadline, the runner abandons the await for that
|
|
105
107
|
* step (reporting it via `onStepDeadline`) and CONTINUES to the next step,
|
|
106
108
|
* each now racing against an already-expired deadline. This is deliberate:
|
|
107
|
-
* the runner keeps iterating so the TERMINAL step (e.g.
|
|
108
|
-
* whose `run()` performs its side effect
|
|
109
|
-
* `await`) still gets to fire. A step that
|
|
110
|
-
* the deadline simply doesn't settle and is
|
|
109
|
+
* the runner keeps iterating so the TERMINAL step (e.g. `coldBootV2`'s
|
|
110
|
+
* `bootstrap-hop`, whose `run()` performs its navigation side effect
|
|
111
|
+
* synchronously before its first `await`) still gets to fire. A step that
|
|
112
|
+
* has nothing to contribute after the deadline simply doesn't settle and is
|
|
113
|
+
* skipped in turn.
|
|
111
114
|
*
|
|
112
115
|
* Per-step timeouts inside `run()` remain the first line of defense and
|
|
113
116
|
* should keep every step well under this budget on a healthy load; this only
|
|
114
|
-
* trips when one of them regresses (
|
|
115
|
-
* omitted there is no overall deadline.
|
|
117
|
+
* trips when one of them regresses (a step hanging past its own timeout).
|
|
118
|
+
* When omitted there is no overall deadline.
|
|
116
119
|
*/
|
|
117
120
|
readonly overallDeadlineMs?: number;
|
|
118
121
|
/**
|
package/src/utils/platform.ts
CHANGED
|
@@ -98,3 +98,24 @@ export function setPlatformOS(os: PlatformOS): void {
|
|
|
98
98
|
(globalThis as any).__REACT_NATIVE_PLATFORM__ = os;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* True only in a real web browser (a DOM is present), false on React Native
|
|
103
|
+
* and Node/SSR.
|
|
104
|
+
*
|
|
105
|
+
* Native defines a global `window` but no `document`, so the DOM probe — not a
|
|
106
|
+
* bare `window` check — is the reliable discriminator. This is the single
|
|
107
|
+
* source of truth consumed by `@oxyhq/services` and `@oxyhq/auth` (both dropped
|
|
108
|
+
* their local copies), so every consumer shares the exact same predicate.
|
|
109
|
+
*
|
|
110
|
+
* NOTE: this is a live runtime probe (not the cached `getPlatformOS()` verdict)
|
|
111
|
+
* because it must reflect the actual DOM availability at call time in the
|
|
112
|
+
* consumer's bundle.
|
|
113
|
+
*/
|
|
114
|
+
export function isWebBrowser(): boolean {
|
|
115
|
+
return (
|
|
116
|
+
typeof window !== 'undefined' &&
|
|
117
|
+
typeof document !== 'undefined' &&
|
|
118
|
+
typeof document.documentElement !== 'undefined'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registrable-apex (eTLD+1) host kernel.
|
|
3
|
+
*
|
|
4
|
+
* The client FAPI auto-detection helper was removed in the device-first cutover
|
|
5
|
+
* (which is why this file is now named for what it actually is, not the old
|
|
6
|
+
* `fapiAutoDetect`). What survives is the pure registrable-domain kernel, still
|
|
7
|
+
* used server-side by the api's device-first same-apex trust checks
|
|
8
|
+
* (`deviceAuth.ts`'s `POST /auth/device/web-session`, via `sameSite.ts`'s
|
|
9
|
+
* `isSameSiteTrustedRequest`) and the `@oxyhq/core/server` CORS/re-export layer.
|
|
10
|
+
*
|
|
11
|
+
* `registrableApex` is NOT legacy — the device-first same-apex check is a live
|
|
12
|
+
* consumer, so this kernel stays regardless of the SSO/FedCM removal.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getDomain } from 'tldts';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Compute the bare registrable apex (eTLD+1) of a hostname using the Public
|
|
19
|
+
* Suffix List, including private hosted suffixes.
|
|
20
|
+
*
|
|
21
|
+
* Performs NO protocol handling and builds NO URL — it only answers "what is
|
|
22
|
+
* the registrable domain of this host, or is that undefinable?".
|
|
23
|
+
*
|
|
24
|
+
* Returns `null` (apex undefinable) for:
|
|
25
|
+
* - empty input;
|
|
26
|
+
* - IPv4 literals (`192.168.1.10`);
|
|
27
|
+
* - IPv6 literals or any host carrying a port (`[::1]`, anything with `:`);
|
|
28
|
+
* - single-label hosts (`intranet`, `localhost`);
|
|
29
|
+
* - public suffixes without a registrable label (e.g. `co.uk`, `github.io`).
|
|
30
|
+
*
|
|
31
|
+
* @param hostname - A bare hostname (no scheme), e.g. `www.mention.earth`.
|
|
32
|
+
* @returns The eTLD+1 (`mention.earth`), or `null` when undefinable.
|
|
33
|
+
*/
|
|
34
|
+
export function registrableApex(hostname: string): string | null {
|
|
35
|
+
if (!hostname) return null;
|
|
36
|
+
const host = hostname.toLowerCase();
|
|
37
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) return null;
|
|
38
|
+
// IPv6 literals are bracketed; any remaining ':' implies a port — neither
|
|
39
|
+
// yields a registrable apex.
|
|
40
|
+
if (host.startsWith('[') || host.includes(':')) return null;
|
|
41
|
+
// The Public Suffix List (private suffixes included) is the source of truth
|
|
42
|
+
// for what an attacker can register. A multi-part public suffix like `co.uk`
|
|
43
|
+
// or a hosted suffix like `github.io` returns no registrable domain.
|
|
44
|
+
return getDomain(host, { allowPrivateDomains: true });
|
|
45
|
+
}
|