@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,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-detect of `auth.<rp-apex>` from `window.location` for the Clerk-style
|
|
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.
|
|
7
|
-
*
|
|
8
|
-
* Ported verbatim from packages/auth-sdk/__tests__/utils/fapiAutoDetect.test.ts
|
|
9
|
-
* plus Public Suffix List regression cases that are unique to the core
|
|
10
|
-
* implementation's registrable-apex guard.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { autoDetectAuthWebUrl, registrableApex } from '../fapiAutoDetect';
|
|
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
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('registrableApex', () => {
|
|
133
|
-
it('returns the eTLD+1 for a normal two-label host', () => {
|
|
134
|
-
expect(registrableApex('mention.earth')).toBe('mention.earth');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('strips subdomains down to the trailing two labels', () => {
|
|
138
|
-
expect(registrableApex('www.mention.earth')).toBe('mention.earth');
|
|
139
|
-
expect(registrableApex('deep.app.homiio.com')).toBe('homiio.com');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('lower-cases the input', () => {
|
|
143
|
-
expect(registrableApex('WWW.Mention.EARTH')).toBe('mention.earth');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('returns the eTLD+1 for hosts under multi-part public suffixes', () => {
|
|
147
|
-
expect(registrableApex('foo.co.uk')).toBe('foo.co.uk');
|
|
148
|
-
expect(registrableApex('www.foo.co.uk')).toBe('foo.co.uk');
|
|
149
|
-
expect(registrableApex('shop.victim.com.tr')).toBe('victim.com.tr');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('returns the eTLD+1 for hosts under private hosted suffixes', () => {
|
|
153
|
-
expect(registrableApex('honest.github.io')).toBe('honest.github.io');
|
|
154
|
-
expect(registrableApex('app.victim.pages.dev')).toBe('victim.pages.dev');
|
|
155
|
-
expect(registrableApex('site.victim.netlify.app')).toBe('victim.netlify.app');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('returns null for bare public and private suffixes', () => {
|
|
159
|
-
expect(registrableApex('co.uk')).toBeNull();
|
|
160
|
-
expect(registrableApex('github.io')).toBeNull();
|
|
161
|
-
expect(registrableApex('pages.dev')).toBeNull();
|
|
162
|
-
expect(registrableApex('netlify.app')).toBeNull();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('returns null for IPv4 literals', () => {
|
|
166
|
-
expect(registrableApex('192.168.1.10')).toBeNull();
|
|
167
|
-
expect(registrableApex('10.0.0.1')).toBeNull();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('returns null for IPv6 literals and hosts carrying a port', () => {
|
|
171
|
-
expect(registrableApex('[::1]')).toBeNull();
|
|
172
|
-
expect(registrableApex('mention.earth:3000')).toBeNull();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('returns null for single-label hosts', () => {
|
|
176
|
-
expect(registrableApex('intranet')).toBeNull();
|
|
177
|
-
expect(registrableApex('localhost')).toBeNull();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('returns null for empty input', () => {
|
|
181
|
-
expect(registrableApex('')).toBeNull();
|
|
182
|
-
});
|
|
183
|
-
});
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Central SSO bounce helpers — per-origin key builders, the bounce URL builder,
|
|
3
|
-
* the central-IdP predicate, and the self-healing guard.
|
|
4
|
-
*
|
|
5
|
-
* These are a wire/storage contract shared with the IdP and every consumer, so
|
|
6
|
-
* the exact key strings, the 30s TTL, and the same-origin / parse-failure
|
|
7
|
-
* behaviour are all asserted explicitly.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
SSO_CALLBACK_PATH,
|
|
12
|
-
SSO_GUARD_TTL_MS,
|
|
13
|
-
ssoStateKey,
|
|
14
|
-
ssoGuardKey,
|
|
15
|
-
ssoDestKey,
|
|
16
|
-
ssoNoSessionKey,
|
|
17
|
-
ssoAttemptedKey,
|
|
18
|
-
ssoPriorSessionKey,
|
|
19
|
-
ssoSignedOutKey,
|
|
20
|
-
buildSsoBounceUrl,
|
|
21
|
-
isCentralIdPOrigin,
|
|
22
|
-
guardActive,
|
|
23
|
-
silentRestoreSuppressed,
|
|
24
|
-
allowSsoBounce,
|
|
25
|
-
} from '../ssoBounce';
|
|
26
|
-
import { CENTRAL_AUTH_URL } from '../authWebUrl';
|
|
27
|
-
|
|
28
|
-
describe('SSO bounce constants', () => {
|
|
29
|
-
it('pins the callback path and guard TTL (wire/storage contract)', () => {
|
|
30
|
-
expect(SSO_CALLBACK_PATH).toBe('/__oxy/sso-callback');
|
|
31
|
-
expect(SSO_GUARD_TTL_MS).toBe(30_000);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('per-origin key builders', () => {
|
|
36
|
-
const origin = 'https://mention.earth';
|
|
37
|
-
|
|
38
|
-
it('builds the exact contracted key strings', () => {
|
|
39
|
-
expect(ssoStateKey(origin)).toBe('oxy_sso_state:https://mention.earth');
|
|
40
|
-
expect(ssoGuardKey(origin)).toBe('oxy_sso_guard:https://mention.earth');
|
|
41
|
-
expect(ssoDestKey(origin)).toBe('oxy_sso_dest:https://mention.earth');
|
|
42
|
-
expect(ssoNoSessionKey(origin)).toBe('oxy_sso_no_session:https://mention.earth');
|
|
43
|
-
expect(ssoAttemptedKey(origin)).toBe('oxy_sso_attempted:https://mention.earth');
|
|
44
|
-
expect(ssoPriorSessionKey(origin)).toBe('oxy_sso_prior_session:https://mention.earth');
|
|
45
|
-
expect(ssoSignedOutKey(origin)).toBe('oxy_signed_out:https://mention.earth');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('namespaces keys per origin so two RPs never collide', () => {
|
|
49
|
-
expect(ssoStateKey('https://a.test')).not.toBe(ssoStateKey('https://b.test'));
|
|
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);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('allowSsoBounce (smart returning-visitor gate)', () => {
|
|
93
|
-
it('ALLOWS a returning visitor (prior-session hint) with no local session', () => {
|
|
94
|
-
// The core of fix B: a returning user whose local session has expired still
|
|
95
|
-
// gets ONE establish bounce so a central-only cross-domain session recovers.
|
|
96
|
-
expect(
|
|
97
|
-
allowSsoBounce({ hasPriorSession: true, hasLocalSession: false }),
|
|
98
|
-
).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('SUPPRESSES a truly first-time anonymous visitor (no hint, no local session)', () => {
|
|
102
|
-
// The smart default (the ONLY behaviour): a first-time visitor browses
|
|
103
|
-
// anonymously instead of being force-redirected to the central IdP.
|
|
104
|
-
expect(
|
|
105
|
-
allowSsoBounce({ hasPriorSession: false, hasLocalSession: false }),
|
|
106
|
-
).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('ALLOWS when a local session was recovered this boot (spec fidelity)', () => {
|
|
110
|
-
expect(
|
|
111
|
-
allowSsoBounce({ hasPriorSession: false, hasLocalSession: true }),
|
|
112
|
-
).toBe(true);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('buildSsoBounceUrl', () => {
|
|
117
|
-
const origin = 'https://mention.earth';
|
|
118
|
-
|
|
119
|
-
it('targets the central IdP /sso with all required params (default base)', () => {
|
|
120
|
-
const url = new URL(buildSsoBounceUrl(origin, 'state-123'));
|
|
121
|
-
|
|
122
|
-
expect(url.origin).toBe('https://auth.oxy.so');
|
|
123
|
-
expect(url.pathname).toBe('/sso');
|
|
124
|
-
expect(url.searchParams.get('prompt')).toBe('none');
|
|
125
|
-
expect(url.searchParams.get('client_id')).toBe(origin);
|
|
126
|
-
expect(url.searchParams.get('return_to')).toBe(origin + SSO_CALLBACK_PATH);
|
|
127
|
-
expect(url.searchParams.get('state')).toBe('state-123');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('honours an explicit authWebUrl override (staging IdP)', () => {
|
|
131
|
-
const url = new URL(
|
|
132
|
-
buildSsoBounceUrl(origin, 'state-xyz', 'https://auth.mention.earth'),
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
expect(url.origin).toBe('https://auth.mention.earth');
|
|
136
|
-
expect(url.pathname).toBe('/sso');
|
|
137
|
-
expect(url.searchParams.get('client_id')).toBe(origin);
|
|
138
|
-
expect(url.searchParams.get('state')).toBe('state-xyz');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('falls back to the central default for an empty override', () => {
|
|
142
|
-
const url = new URL(buildSsoBounceUrl(origin, 's', undefined));
|
|
143
|
-
expect(url.origin).toBe('https://auth.oxy.so');
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe('isCentralIdPOrigin', () => {
|
|
148
|
-
it('matches the central IdP origin', () => {
|
|
149
|
-
expect(isCentralIdPOrigin('https://auth.oxy.so')).toBe(true);
|
|
150
|
-
expect(isCentralIdPOrigin(CENTRAL_AUTH_URL)).toBe(true);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('normalises a trailing-slash / path candidate via URL origin', () => {
|
|
154
|
-
expect(isCentralIdPOrigin('https://auth.oxy.so/')).toBe(true);
|
|
155
|
-
expect(isCentralIdPOrigin('https://auth.oxy.so/sso')).toBe(true);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('rejects a non-central origin', () => {
|
|
159
|
-
expect(isCentralIdPOrigin('https://mention.earth')).toBe(false);
|
|
160
|
-
expect(isCentralIdPOrigin('https://auth.mention.earth')).toBe(false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('returns false for an unparseable candidate', () => {
|
|
164
|
-
expect(isCentralIdPOrigin('not a url')).toBe(false);
|
|
165
|
-
expect(isCentralIdPOrigin('')).toBe(false);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('guardActive', () => {
|
|
170
|
-
const origin = 'https://mention.earth';
|
|
171
|
-
|
|
172
|
-
function storageWith(value: string | null): Pick<Storage, 'getItem'> {
|
|
173
|
-
return { getItem: (key: string) => (key === ssoGuardKey(origin) ? value : null) };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
it('is active for a present, fresh guard', () => {
|
|
177
|
-
const now = 1_000_000;
|
|
178
|
-
const storage = storageWith(String(now - 1_000)); // 1s old, well under TTL
|
|
179
|
-
expect(guardActive(storage, origin, now)).toBe(true);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('is inactive for a present but stale guard (older than TTL)', () => {
|
|
183
|
-
const now = 1_000_000;
|
|
184
|
-
const storage = storageWith(String(now - SSO_GUARD_TTL_MS - 1));
|
|
185
|
-
expect(guardActive(storage, origin, now)).toBe(false);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('is inactive for a guard exactly at the TTL boundary (strict <)', () => {
|
|
189
|
-
const now = 1_000_000;
|
|
190
|
-
const storage = storageWith(String(now - SSO_GUARD_TTL_MS));
|
|
191
|
-
expect(guardActive(storage, origin, now)).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('is inactive when the guard is missing', () => {
|
|
195
|
-
expect(guardActive(storageWith(null), origin, 1_000_000)).toBe(false);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('is inactive for an empty-string guard value', () => {
|
|
199
|
-
expect(guardActive(storageWith(''), origin, 1_000_000)).toBe(false);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('is inactive for a malformed (non-numeric) guard value', () => {
|
|
203
|
-
expect(guardActive(storageWith('not-a-number'), origin, 1_000_000)).toBe(false);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('is inactive (never throws) when getItem itself throws', () => {
|
|
207
|
-
const throwing: Pick<Storage, 'getItem'> = {
|
|
208
|
-
getItem: () => {
|
|
209
|
-
throw new Error('storage locked');
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
expect(guardActive(throwing, origin, 1_000_000)).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('defaults now to Date.now() when omitted', () => {
|
|
216
|
-
const storage = storageWith(String(Date.now() - 500));
|
|
217
|
-
expect(guardActive(storage, origin)).toBe(true);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `establishIdpSessionAfterClaim` — the post-claim durable-session hop.
|
|
3
|
-
*
|
|
4
|
-
* After a WEB device-flow ("Sign in with Oxy" QR) claim commits, the app holds
|
|
5
|
-
* only in-memory tokens: no `fedcm_session` cookie was ever planted at the IdP,
|
|
6
|
-
* so a reload cannot re-mint a token. This primitive performs ONE top-level
|
|
7
|
-
* establish hop (via the RP-minted establish-URL) so the per-apex IdP cookie is
|
|
8
|
-
* planted and a reload restores via the existing `sso-return` / silent-iframe
|
|
9
|
-
* paths.
|
|
10
|
-
*
|
|
11
|
-
* Invariants pinned here:
|
|
12
|
-
* - web only (no-op off-web / native);
|
|
13
|
-
* - never fires while sitting on the central IdP origin (would loop);
|
|
14
|
-
* - persists the SAME bounce state (`ssoStateKey`/`ssoGuardKey`/`ssoDestKey`)
|
|
15
|
-
* the `buildSsoBounceUrl` machinery primes, so the post-bounce `sso-return`
|
|
16
|
-
* step validates the state, exchanges the code, and restores the dest;
|
|
17
|
-
* - persists ONLY after the establish-URL request succeeds (no stale state on
|
|
18
|
-
* failure);
|
|
19
|
-
* - navigates exactly once, to the server-returned establish URL;
|
|
20
|
-
* - total: an establish-request failure leaves the committed in-memory session
|
|
21
|
-
* untouched, does NOT navigate, and returns `false` (user no worse off).
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { establishIdpSessionAfterClaim } from '../ssoEstablish';
|
|
25
|
-
import {
|
|
26
|
-
ssoStateKey,
|
|
27
|
-
ssoGuardKey,
|
|
28
|
-
ssoDestKey,
|
|
29
|
-
} from '../ssoBounce';
|
|
30
|
-
import { CENTRAL_AUTH_URL } from '../authWebUrl';
|
|
31
|
-
|
|
32
|
-
const RP_ORIGIN = 'https://accounts.oxy.so';
|
|
33
|
-
const RP_HREF = 'https://accounts.oxy.so/settings';
|
|
34
|
-
const ESTABLISH_URL =
|
|
35
|
-
'https://auth.oxy.so/sso/establish?et=jwt.abc.def&return_to=https%3A%2F%2Faccounts.oxy.so%2F__oxy%2Fsso-callback&state=state-xyz';
|
|
36
|
-
|
|
37
|
-
function makeStorage(): {
|
|
38
|
-
storage: Pick<Storage, 'getItem' | 'setItem'>;
|
|
39
|
-
map: Map<string, string>;
|
|
40
|
-
} {
|
|
41
|
-
const map = new Map<string, string>();
|
|
42
|
-
return {
|
|
43
|
-
map,
|
|
44
|
-
storage: {
|
|
45
|
-
getItem: (k: string) => (map.has(k) ? (map.get(k) as string) : null),
|
|
46
|
-
setItem: (k: string, v: string) => {
|
|
47
|
-
map.set(k, v);
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
describe('establishIdpSessionAfterClaim', () => {
|
|
54
|
-
it('requests the establish URL with the RP origin + generated state and navigates once (web)', async () => {
|
|
55
|
-
const { storage, map } = makeStorage();
|
|
56
|
-
const navigated: string[] = [];
|
|
57
|
-
const requestSsoEstablishUrl = jest.fn(async (origin: string, state: string) => {
|
|
58
|
-
expect(origin).toBe(RP_ORIGIN);
|
|
59
|
-
expect(state).toBe('state-xyz');
|
|
60
|
-
return { establishUrl: ESTABLISH_URL };
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const result = await establishIdpSessionAfterClaim(
|
|
64
|
-
{ requestSsoEstablishUrl },
|
|
65
|
-
{
|
|
66
|
-
isWeb: () => true,
|
|
67
|
-
storage,
|
|
68
|
-
location: { origin: RP_ORIGIN, href: RP_HREF },
|
|
69
|
-
navigate: (url) => navigated.push(url),
|
|
70
|
-
generateState: () => 'state-xyz',
|
|
71
|
-
now: () => 1_700_000_000_000,
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
expect(result).toBe(true);
|
|
76
|
-
expect(requestSsoEstablishUrl).toHaveBeenCalledTimes(1);
|
|
77
|
-
expect(navigated).toEqual([ESTABLISH_URL]);
|
|
78
|
-
|
|
79
|
-
// Bounce state persisted so the post-bounce `sso-return` step validates it.
|
|
80
|
-
expect(map.get(ssoStateKey(RP_ORIGIN))).toBe('state-xyz');
|
|
81
|
-
expect(map.get(ssoGuardKey(RP_ORIGIN))).toBe(String(1_700_000_000_000));
|
|
82
|
-
expect(map.get(ssoDestKey(RP_ORIGIN))).toBe(RP_HREF);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('is a no-op off-web (native): no request, no navigation, no state', async () => {
|
|
86
|
-
const { storage, map } = makeStorage();
|
|
87
|
-
const navigated: string[] = [];
|
|
88
|
-
const requestSsoEstablishUrl = jest.fn(async () => ({ establishUrl: ESTABLISH_URL }));
|
|
89
|
-
|
|
90
|
-
const result = await establishIdpSessionAfterClaim(
|
|
91
|
-
{ requestSsoEstablishUrl },
|
|
92
|
-
{
|
|
93
|
-
isWeb: () => false,
|
|
94
|
-
storage,
|
|
95
|
-
location: { origin: RP_ORIGIN, href: RP_HREF },
|
|
96
|
-
navigate: (url) => navigated.push(url),
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
expect(result).toBe(false);
|
|
101
|
-
expect(requestSsoEstablishUrl).not.toHaveBeenCalled();
|
|
102
|
-
expect(navigated).toEqual([]);
|
|
103
|
-
expect(map.size).toBe(0);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('never fires while sitting on the central IdP origin (loop guard)', async () => {
|
|
107
|
-
const { storage, map } = makeStorage();
|
|
108
|
-
const navigated: string[] = [];
|
|
109
|
-
const requestSsoEstablishUrl = jest.fn(async () => ({ establishUrl: ESTABLISH_URL }));
|
|
110
|
-
|
|
111
|
-
const result = await establishIdpSessionAfterClaim(
|
|
112
|
-
{ requestSsoEstablishUrl },
|
|
113
|
-
{
|
|
114
|
-
isWeb: () => true,
|
|
115
|
-
storage,
|
|
116
|
-
location: { origin: new URL(CENTRAL_AUTH_URL).origin, href: `${CENTRAL_AUTH_URL}/` },
|
|
117
|
-
navigate: (url) => navigated.push(url),
|
|
118
|
-
},
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
expect(result).toBe(false);
|
|
122
|
-
expect(requestSsoEstablishUrl).not.toHaveBeenCalled();
|
|
123
|
-
expect(navigated).toEqual([]);
|
|
124
|
-
expect(map.size).toBe(0);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('on an establish-request failure: no navigation, no persisted state, returns false (single attempt)', async () => {
|
|
128
|
-
const { storage, map } = makeStorage();
|
|
129
|
-
const navigated: string[] = [];
|
|
130
|
-
const onError = jest.fn();
|
|
131
|
-
const requestSsoEstablishUrl = jest.fn(async () => {
|
|
132
|
-
throw new Error('403 unapproved origin');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const result = await establishIdpSessionAfterClaim(
|
|
136
|
-
{ requestSsoEstablishUrl },
|
|
137
|
-
{
|
|
138
|
-
isWeb: () => true,
|
|
139
|
-
storage,
|
|
140
|
-
location: { origin: RP_ORIGIN, href: RP_HREF },
|
|
141
|
-
navigate: (url) => navigated.push(url),
|
|
142
|
-
generateState: () => 'state-xyz',
|
|
143
|
-
onError,
|
|
144
|
-
},
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
expect(result).toBe(false);
|
|
148
|
-
expect(requestSsoEstablishUrl).toHaveBeenCalledTimes(1);
|
|
149
|
-
expect(navigated).toEqual([]);
|
|
150
|
-
// No stale bounce state left behind on failure.
|
|
151
|
-
expect(map.size).toBe(0);
|
|
152
|
-
expect(onError).toHaveBeenCalledTimes(1);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('does not navigate when the server returns no establish URL', async () => {
|
|
156
|
-
const { storage, map } = makeStorage();
|
|
157
|
-
const navigated: string[] = [];
|
|
158
|
-
const requestSsoEstablishUrl = jest.fn(async () => ({ establishUrl: '' }));
|
|
159
|
-
|
|
160
|
-
const result = await establishIdpSessionAfterClaim(
|
|
161
|
-
{ requestSsoEstablishUrl },
|
|
162
|
-
{
|
|
163
|
-
isWeb: () => true,
|
|
164
|
-
storage,
|
|
165
|
-
location: { origin: RP_ORIGIN, href: RP_HREF },
|
|
166
|
-
navigate: (url) => navigated.push(url),
|
|
167
|
-
generateState: () => 'state-xyz',
|
|
168
|
-
},
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
expect(result).toBe(false);
|
|
172
|
-
expect(navigated).toEqual([]);
|
|
173
|
-
expect(map.size).toBe(0);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it.each([
|
|
177
|
-
['unparseable', 'not a url'],
|
|
178
|
-
['non-https (http)', 'http://auth.oxy.so/sso/establish?et=x&state=s'],
|
|
179
|
-
['wrong path', 'https://auth.oxy.so/evil?et=x&state=s'],
|
|
180
|
-
['non-auth host', 'https://evil.oxy.so/sso/establish?et=x&state=s'],
|
|
181
|
-
])(
|
|
182
|
-
'aborts silently (no navigation, no state) for a %s establish URL',
|
|
183
|
-
async (_label, badUrl) => {
|
|
184
|
-
const { storage, map } = makeStorage();
|
|
185
|
-
const navigated: string[] = [];
|
|
186
|
-
const requestSsoEstablishUrl = jest.fn(async () => ({ establishUrl: badUrl }));
|
|
187
|
-
|
|
188
|
-
const result = await establishIdpSessionAfterClaim(
|
|
189
|
-
{ requestSsoEstablishUrl },
|
|
190
|
-
{
|
|
191
|
-
isWeb: () => true,
|
|
192
|
-
storage,
|
|
193
|
-
location: { origin: RP_ORIGIN, href: RP_HREF },
|
|
194
|
-
navigate: (url) => navigated.push(url),
|
|
195
|
-
generateState: () => 'state-xyz',
|
|
196
|
-
},
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
expect(result).toBe(false);
|
|
200
|
-
expect(navigated).toEqual([]);
|
|
201
|
-
expect(map.size).toBe(0);
|
|
202
|
-
},
|
|
203
|
-
);
|
|
204
|
-
});
|