@oxyhq/core 5.5.0 → 6.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 +6 -3
- 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 +47 -44
- package/dist/cjs/mixins/OxyServices.accounts.js +6 -13
- package/dist/cjs/mixins/OxyServices.auth.js +66 -201
- package/dist/cjs/mixins/OxyServices.authorizedApps.js +38 -0
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +119 -0
- package/dist/cjs/mixins/index.js +13 -17
- 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/utils/accountUtils.js +1 -55
- package/dist/cjs/utils/authWebUrl.js +6 -15
- package/dist/cjs/utils/fapiAutoDetect.js +16 -64
- package/dist/cjs/utils/platform.js +19 -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 +6 -3
- 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 +28 -18
- package/dist/esm/mixins/OxyServices.accounts.js +6 -13
- package/dist/esm/mixins/OxyServices.auth.js +66 -201
- package/dist/esm/mixins/OxyServices.authorizedApps.js +35 -0
- package/dist/esm/mixins/OxyServices.deviceBoot.js +116 -0
- package/dist/esm/mixins/index.js +13 -17
- 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/utils/accountUtils.js +0 -53
- package/dist/esm/utils/authWebUrl.js +6 -14
- package/dist/esm/utils/fapiAutoDetect.js +16 -63
- package/dist/esm/utils/platform.js +18 -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 +13 -0
- 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 +15 -21
- package/dist/types/mixins/OxyServices.accounts.d.ts +0 -2
- 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.redirect.d.ts → OxyServices.authorizedApps.d.ts} +35 -33
- 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 +6 -9
- package/dist/types/models/interfaces.d.ts +0 -67
- package/dist/types/session/SessionClient.d.ts +25 -0
- package/dist/types/session/authStateStore.d.ts +119 -0
- package/dist/types/session/refresh.d.ts +93 -0
- package/dist/types/utils/accountUtils.d.ts +0 -14
- package/dist/types/utils/authWebUrl.d.ts +6 -12
- package/dist/types/utils/fapiAutoDetect.d.ts +15 -38
- package/dist/types/utils/platform.d.ts +14 -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 +19 -3
- 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 +74 -65
- package/src/mixins/OxyServices.accounts.ts +6 -13
- package/src/mixins/OxyServices.auth.ts +78 -253
- package/src/mixins/OxyServices.authorizedApps.ts +75 -0
- 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__/authorizedApps.test.ts +63 -0
- package/src/mixins/__tests__/passwordSignIn.test.ts +91 -0
- package/src/mixins/index.ts +16 -22
- package/src/models/interfaces.ts +0 -79
- package/src/session/SessionClient.ts +52 -1
- 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/utils/__tests__/authWebUrl.test.ts +5 -29
- package/src/utils/__tests__/fapiAutoDetect.test.ts +5 -126
- package/src/utils/__tests__/validationUtils.test.ts +30 -0
- package/src/utils/accountUtils.ts +0 -62
- package/src/utils/authWebUrl.ts +6 -15
- package/src/utils/fapiAutoDetect.ts +16 -60
- package/src/utils/platform.ts +21 -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/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/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.silent.d.ts +0 -132
- package/dist/types/mixins/OxyServices.sso.d.ts +0 -138
- 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__/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/ssoEstablish.ts +0 -174
- package/src/utils/ssoReturn.ts +0 -389
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Central IdP apex/origin constants.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `resolveCentralAuthUrl` was removed in the device-first cutover (the SDK no
|
|
5
|
+
* longer resolves an IdP host). The `CENTRAL_IDP_APEX` / `CENTRAL_AUTH_URL`
|
|
6
|
+
* constants survive because the IdP worker imports them to brand assertions.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX
|
|
9
|
+
import { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX } from '../authWebUrl';
|
|
9
10
|
|
|
10
11
|
describe('CENTRAL_IDP_APEX', () => {
|
|
11
12
|
it('is the central IdP registrable apex', () => {
|
|
@@ -23,28 +24,3 @@ describe('CENTRAL_AUTH_URL', () => {
|
|
|
23
24
|
expect(CENTRAL_AUTH_URL).toBe(`https://auth.${CENTRAL_IDP_APEX}`);
|
|
24
25
|
});
|
|
25
26
|
});
|
|
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
|
-
});
|
|
@@ -1,133 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* multi-domain FAPI setup. The IdP backend independently derives `iss` and
|
|
4
|
-
* the FedCM manifest from the request host, so the only contract the SDK
|
|
5
|
-
* needs to honour is: build URLs against the same host the page is on,
|
|
6
|
-
* one subdomain over.
|
|
2
|
+
* `registrableApex` — the eTLD+1 host kernel (Public Suffix List aware).
|
|
7
3
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
function loc(hostname: string, protocol = 'https:'): Pick<Location, 'hostname' | 'protocol'> {
|
|
16
|
-
return { hostname, protocol };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('autoDetectAuthWebUrl', () => {
|
|
20
|
-
describe('returns auth.<apex> for public hostnames', () => {
|
|
21
|
-
it('derives from the apex itself', () => {
|
|
22
|
-
expect(autoDetectAuthWebUrl(loc('mention.earth'))).toBe('https://auth.mention.earth');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('strips one leading subdomain', () => {
|
|
26
|
-
expect(autoDetectAuthWebUrl(loc('www.mention.earth'))).toBe('https://auth.mention.earth');
|
|
27
|
-
expect(autoDetectAuthWebUrl(loc('app.alia.onl'))).toBe('https://auth.alia.onl');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('strips multiple leading subdomains down to the last two labels', () => {
|
|
31
|
-
// The heuristic is "last two labels" — fine for our use case because
|
|
32
|
-
// the IdP itself validates the request host. Deeply-nested hostnames
|
|
33
|
-
// resolve to the trailing two-label apex.
|
|
34
|
-
expect(autoDetectAuthWebUrl(loc('deep.app.homiio.com'))).toBe('https://auth.homiio.com');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('honours the request protocol so dev http stays http', () => {
|
|
38
|
-
expect(autoDetectAuthWebUrl(loc('staging.example.test', 'http:'))).toBe(
|
|
39
|
-
'http://auth.example.test'
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('returns current origin when already on the IdP', () => {
|
|
45
|
-
it('keeps everything same-origin instead of hopping to a sibling IdP', () => {
|
|
46
|
-
expect(autoDetectAuthWebUrl(loc('auth.mention.earth'))).toBe('https://auth.mention.earth');
|
|
47
|
-
expect(autoDetectAuthWebUrl(loc('auth.oxy.so'))).toBe('https://auth.oxy.so');
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('returns undefined where auto-detect would be wrong', () => {
|
|
52
|
-
it('skips localhost and 127.0.0.1 (dev)', () => {
|
|
53
|
-
expect(autoDetectAuthWebUrl(loc('localhost', 'http:'))).toBeUndefined();
|
|
54
|
-
expect(autoDetectAuthWebUrl(loc('127.0.0.1', 'http:'))).toBeUndefined();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('skips IPv4 literals', () => {
|
|
58
|
-
expect(autoDetectAuthWebUrl(loc('192.168.1.10'))).toBeUndefined();
|
|
59
|
-
expect(autoDetectAuthWebUrl(loc('10.0.0.1'))).toBeUndefined();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('skips IPv6 literals (bracketed)', () => {
|
|
63
|
-
expect(autoDetectAuthWebUrl(loc('[::1]'))).toBeUndefined();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('skips single-label hostnames', () => {
|
|
67
|
-
expect(autoDetectAuthWebUrl(loc('intranet'))).toBeUndefined();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('skips unknown protocols', () => {
|
|
71
|
-
expect(autoDetectAuthWebUrl({ hostname: 'mention.earth', protocol: 'file:' })).toBeUndefined();
|
|
72
|
-
expect(autoDetectAuthWebUrl({ hostname: 'mention.earth', protocol: 'ftp:' })).toBeUndefined();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('skips empty/missing hostnames', () => {
|
|
76
|
-
expect(autoDetectAuthWebUrl(loc(''))).toBeUndefined();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('returns undefined when no location is available (SSR / non-browser)', () => {
|
|
80
|
-
expect(autoDetectAuthWebUrl(undefined)).toBeUndefined();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('uses the Public Suffix List for registrable apex detection', () => {
|
|
85
|
-
it('derives from the registrable domain under multi-part public suffixes', () => {
|
|
86
|
-
expect(autoDetectAuthWebUrl(loc('foo.co.uk'))).toBe('https://auth.foo.co.uk');
|
|
87
|
-
expect(autoDetectAuthWebUrl(loc('shop.com.au'))).toBe('https://auth.shop.com.au');
|
|
88
|
-
expect(autoDetectAuthWebUrl(loc('shop.victim.com.tr'))).toBe('https://auth.victim.com.tr');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('derives from the registrable domain under private hosted suffixes', () => {
|
|
92
|
-
expect(autoDetectAuthWebUrl(loc('honest.github.io'))).toBe('https://auth.honest.github.io');
|
|
93
|
-
expect(autoDetectAuthWebUrl(loc('app.victim.pages.dev'))).toBe(
|
|
94
|
-
'https://auth.victim.pages.dev'
|
|
95
|
-
);
|
|
96
|
-
expect(autoDetectAuthWebUrl(loc('site.victim.netlify.app'))).toBe(
|
|
97
|
-
'https://auth.victim.netlify.app'
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('returns undefined for bare public and private suffixes', () => {
|
|
102
|
-
expect(autoDetectAuthWebUrl(loc('co.uk'))).toBeUndefined();
|
|
103
|
-
expect(autoDetectAuthWebUrl(loc('github.io'))).toBeUndefined();
|
|
104
|
-
expect(autoDetectAuthWebUrl(loc('pages.dev'))).toBeUndefined();
|
|
105
|
-
expect(autoDetectAuthWebUrl(loc('netlify.app'))).toBeUndefined();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Regression guard: the refactor to delegate host handling to `registrableApex`
|
|
110
|
-
// must not change any of the pre-existing return values.
|
|
111
|
-
describe('regression — unchanged values after registrableApex extraction', () => {
|
|
112
|
-
const cases: Array<[string, string | undefined]> = [
|
|
113
|
-
['mention.earth', 'https://auth.mention.earth'],
|
|
114
|
-
['www.mention.earth', 'https://auth.mention.earth'],
|
|
115
|
-
['deep.app.homiio.com', 'https://auth.homiio.com'],
|
|
116
|
-
['auth.oxy.so', 'https://auth.oxy.so'],
|
|
117
|
-
['auth.mention.earth', 'https://auth.mention.earth'],
|
|
118
|
-
['localhost', undefined],
|
|
119
|
-
['192.168.1.10', undefined],
|
|
120
|
-
['[::1]', undefined],
|
|
121
|
-
['intranet', undefined],
|
|
122
|
-
['', undefined],
|
|
123
|
-
['foo.co.uk', 'https://auth.foo.co.uk'],
|
|
124
|
-
];
|
|
125
|
-
it.each(cases)('autoDetectAuthWebUrl(%s) === %s', (hostname, expected) => {
|
|
126
|
-
const protocol = hostname === 'localhost' || hostname === '[::1]' ? 'http:' : 'https:';
|
|
127
|
-
expect(autoDetectAuthWebUrl(loc(hostname, protocol))).toBe(expected);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
});
|
|
9
|
+
import { registrableApex } from '../fapiAutoDetect';
|
|
131
10
|
|
|
132
11
|
describe('registrableApex', () => {
|
|
133
12
|
it('returns the eTLD+1 for a normal two-label host', () => {
|
|
@@ -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;
|
|
@@ -178,67 +177,6 @@ export const createQuickAccount = (
|
|
|
178
177
|
};
|
|
179
178
|
};
|
|
180
179
|
|
|
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
180
|
/**
|
|
243
181
|
* Return the account's preferred Bloom color preset, or `null` if it has no
|
|
244
182
|
* preference. Centralises the `color ?? null` normalisation so consumers can
|
package/src/utils/authWebUrl.ts
CHANGED
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
* `window`/`location`, and has no side effects. It is the single source of
|
|
13
13
|
* truth for the central IdP origin so call sites never hardcode the literal.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* LEGACY(old-sdk): the client resolver (`resolveCentralAuthUrl`) was removed in
|
|
16
|
+
* the device-first cutover; `CENTRAL_IDP_APEX` / `CENTRAL_AUTH_URL` survive ONLY
|
|
17
|
+
* because the lista-B IdP worker imports them to brand assertions. Deletable
|
|
18
|
+
* once Homiio/Allo/Alia/Syra are bumped off the old SDK AND CloudWatch
|
|
19
|
+
* `/oxy/ecs` shows the `/sso*` + `/fedcm/*` routes quiet — F-final sweep.
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -31,17 +33,6 @@ export const CENTRAL_IDP_APEX = 'oxy.so';
|
|
|
31
33
|
/**
|
|
32
34
|
* The canonical central Identity Provider origin for the Oxy ecosystem.
|
|
33
35
|
* No trailing slash. Derived from {@link CENTRAL_IDP_APEX} so the apex and the
|
|
34
|
-
* full origin never drift apart.
|
|
36
|
+
* full origin never drift apart. The IdP worker imports it to brand assertions.
|
|
35
37
|
*/
|
|
36
38
|
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
|
-
}
|
|
@@ -1,37 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* This helper computes `https://auth.<rp-apex>` from
|
|
16
|
-
* `window.location.hostname` so a consuming app doesn't have to pass
|
|
17
|
-
* `authWebUrl` explicitly. Returns `undefined` for environments where
|
|
18
|
-
* auto-detection would be wrong:
|
|
19
|
-
*
|
|
20
|
-
* - SSR / non-browser (no `window`).
|
|
21
|
-
* - `localhost`, `127.0.0.1`, IPv4/IPv6 literals.
|
|
22
|
-
* - Hostnames with fewer than two labels.
|
|
23
|
-
* - Hostnames where a registrable domain cannot be determined from the
|
|
24
|
-
* Public Suffix List, including private hosted suffixes such as
|
|
25
|
-
* `github.io`, `pages.dev`, and `netlify.app`.
|
|
26
|
-
*
|
|
27
|
-
* When the page is already loaded ON the IdP itself (`auth.<anything>`),
|
|
28
|
-
* the helper returns the current origin so the SDK keeps everything
|
|
29
|
-
* same-origin instead of hopping to a different IdP host.
|
|
30
|
-
*
|
|
31
|
-
* The IdP backend independently derives `iss`, `provider_urls`, and the
|
|
32
|
-
* `fedcm.json` icon URLs from the request host
|
|
33
|
-
* (`packages/auth/server/index.ts`), so an honest CNAME pair is all that
|
|
34
|
-
* is required for end-to-end FedCM correctness — no per-RP config.
|
|
2
|
+
* Registrable-apex (eTLD+1) host kernel.
|
|
3
|
+
*
|
|
4
|
+
* The client FAPI auto-detection helper (`autoDetectAuthWebUrl`) was removed in
|
|
5
|
+
* the device-first cutover — the SDK no longer derives a per-apex `auth.<rp>`
|
|
6
|
+
* IdP host. What survives is the pure registrable-domain kernel, still used by
|
|
7
|
+
* the api SSO surface, `fedcm.service`, `deviceAuth` (same-apex trust checks),
|
|
8
|
+
* and the IdP worker (all lista B / server-side). `@oxyhq/core/server`
|
|
9
|
+
* re-exports it for the api.
|
|
10
|
+
*
|
|
11
|
+
* LEGACY(old-sdk): `registrableApex` survives ONLY for the lista-B server/IdP
|
|
12
|
+
* SSO surface. Deletable once Homiio/Allo/Alia/Syra are bumped off the old SDK
|
|
13
|
+
* AND CloudWatch `/oxy/ecs` shows the `/sso*` + `/fedcm/*` routes quiet — the
|
|
14
|
+
* F-final sweep should remove this file then.
|
|
35
15
|
*/
|
|
36
16
|
|
|
37
17
|
import { getDomain } from 'tldts';
|
|
@@ -40,10 +20,8 @@ import { getDomain } from 'tldts';
|
|
|
40
20
|
* Compute the bare registrable apex (eTLD+1) of a hostname using the Public
|
|
41
21
|
* Suffix List, including private hosted suffixes.
|
|
42
22
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* and builds NO URL. It only answers "what is the registrable domain of this
|
|
46
|
-
* host, or is that undefinable?".
|
|
23
|
+
* Performs NO protocol handling and builds NO URL — it only answers "what is
|
|
24
|
+
* the registrable domain of this host, or is that undefinable?".
|
|
47
25
|
*
|
|
48
26
|
* Returns `null` (apex undefinable) for:
|
|
49
27
|
* - empty input;
|
|
@@ -64,28 +42,6 @@ export function registrableApex(hostname: string): string | null {
|
|
|
64
42
|
if (host.startsWith('[') || host.includes(':')) return null;
|
|
65
43
|
// The Public Suffix List (private suffixes included) is the source of truth
|
|
66
44
|
// for what an attacker can register. A multi-part public suffix like `co.uk`
|
|
67
|
-
// or a hosted suffix like `github.io` returns no registrable domain
|
|
68
|
-
// deriving `auth.<apex>` against it is impossible — `getDomain` returns null.
|
|
45
|
+
// or a hosted suffix like `github.io` returns no registrable domain.
|
|
69
46
|
return getDomain(host, { allowPrivateDomains: true });
|
|
70
47
|
}
|
|
71
|
-
|
|
72
|
-
export function autoDetectAuthWebUrl(
|
|
73
|
-
location: Pick<Location, 'hostname' | 'protocol'> | undefined =
|
|
74
|
-
typeof window !== 'undefined' ? window.location : undefined
|
|
75
|
-
): string | undefined {
|
|
76
|
-
if (!location) return undefined;
|
|
77
|
-
const { hostname, protocol } = location;
|
|
78
|
-
if (!hostname) return undefined;
|
|
79
|
-
if (protocol !== 'https:' && protocol !== 'http:') return undefined;
|
|
80
|
-
if (hostname === 'localhost' || hostname === '127.0.0.1') return undefined;
|
|
81
|
-
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) return undefined;
|
|
82
|
-
if (hostname.startsWith('[')) return undefined;
|
|
83
|
-
// Already ON the IdP — keep everything same-origin instead of hopping to a
|
|
84
|
-
// sibling host.
|
|
85
|
-
if (hostname.startsWith('auth.')) {
|
|
86
|
-
return `${protocol}//${hostname}`;
|
|
87
|
-
}
|
|
88
|
-
const apex = registrableApex(hostname);
|
|
89
|
-
if (apex === null) return undefined;
|
|
90
|
-
return `${protocol}//auth.${apex}`;
|
|
91
|
-
}
|
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
|
+
|