@oxyhq/core 9.1.0 → 9.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/boot/{coldBootV2.js → sessionColdBoot.js} +15 -2
- package/dist/cjs/index.js +27 -6
- package/dist/cjs/mixins/OxyServices.accounts.js +3 -0
- package/dist/cjs/mixins/OxyServices.auth.js +61 -0
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +29 -1
- package/dist/cjs/server/index.js +8 -1
- package/dist/cjs/session/SessionClient.js +57 -25
- package/dist/cjs/session/accountDialogController.js +21 -9
- package/dist/cjs/session/authStateStore.js +13 -7
- package/dist/cjs/session/hubSync.js +55 -0
- package/dist/cjs/session/sessionClientHost.js +5 -0
- package/dist/cjs/utils/coldBoot.js +1 -1
- package/dist/cjs/utils/oauthPkce.js +65 -1
- package/dist/cjs/utils/officialOrigins.js +128 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/boot/{coldBootV2.js → sessionColdBoot.js} +15 -2
- package/dist/esm/index.js +5 -2
- package/dist/esm/mixins/OxyServices.accounts.js +1 -0
- package/dist/esm/mixins/OxyServices.auth.js +61 -0
- package/dist/esm/mixins/OxyServices.deviceBoot.js +30 -2
- package/dist/esm/server/index.js +1 -0
- package/dist/esm/session/SessionClient.js +57 -25
- package/dist/esm/session/accountDialogController.js +21 -9
- package/dist/esm/session/authStateStore.js +13 -7
- package/dist/esm/session/hubSync.js +51 -0
- package/dist/esm/session/sessionClientHost.js +5 -0
- package/dist/esm/utils/coldBoot.js +1 -1
- package/dist/esm/utils/oauthPkce.js +60 -0
- package/dist/esm/utils/officialOrigins.js +119 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/boot/{coldBootV2.d.ts → sessionColdBoot.d.ts} +1 -1
- package/dist/types/index.d.ts +9 -5
- package/dist/types/mixins/OxyServices.accounts.d.ts +8 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +15 -0
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +6 -2
- package/dist/types/models/interfaces.d.ts +3 -1
- package/dist/types/models/session.d.ts +6 -0
- package/dist/types/server/index.d.ts +1 -0
- package/dist/types/session/SessionClient.d.ts +6 -0
- package/dist/types/session/accountDialogController.d.ts +9 -1
- package/dist/types/session/authStateStore.d.ts +1 -1
- package/dist/types/session/hubSync.d.ts +20 -0
- package/dist/types/session/sessionClientHost.d.ts +2 -1
- package/dist/types/utils/coldBoot.d.ts +2 -2
- package/dist/types/utils/oauthPkce.d.ts +27 -0
- package/dist/types/utils/officialOrigins.d.ts +17 -0
- package/package.json +2 -2
- package/src/boot/__tests__/{coldBootV2.test.ts → sessionColdBoot.test.ts} +21 -1
- package/src/boot/{coldBootV2.ts → sessionColdBoot.ts} +15 -2
- package/src/index.ts +30 -3
- package/src/mixins/OxyServices.accounts.ts +9 -0
- package/src/mixins/OxyServices.auth.ts +72 -1
- package/src/mixins/OxyServices.deviceBoot.ts +46 -1
- package/src/models/interfaces.ts +3 -1
- package/src/models/session.ts +6 -0
- package/src/server/index.ts +8 -0
- package/src/session/SessionClient.ts +68 -26
- package/src/session/__tests__/SessionClient.additive.test.ts +1 -0
- package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +1 -0
- package/src/session/__tests__/SessionClient.diagnostics.test.ts +1 -0
- package/src/session/__tests__/SessionClient.rest.test.ts +1 -0
- package/src/session/__tests__/SessionClient.socket.test.ts +1 -0
- package/src/session/__tests__/SessionClient.socketFactory.test.ts +1 -0
- package/src/session/__tests__/SessionClient.state.test.ts +1 -0
- package/src/session/__tests__/accountDialogController.test.ts +46 -7
- package/src/session/__tests__/sessionIntegration.test.ts +7 -0
- package/src/session/accountDialogController.ts +45 -11
- package/src/session/authStateStore.ts +18 -9
- package/src/session/hubSync.ts +79 -0
- package/src/session/sessionClientHost.ts +10 -2
- package/src/utils/__tests__/officialOrigins.test.ts +74 -0
- package/src/utils/coldBoot.ts +2 -2
- package/src/utils/oauthPkce.ts +71 -0
- package/src/utils/officialOrigins.ts +124 -0
|
@@ -17,11 +17,15 @@
|
|
|
17
17
|
* React Native. No `require()`, so the ESM build stays bundler-clean.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.DEFAULT_OAUTH_SCOPE = exports.OXY_AUTHORIZE_URL = void 0;
|
|
20
|
+
exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.DEFAULT_OAUTH_SCOPE = exports.OXY_AUTHORIZE_URL = void 0;
|
|
21
21
|
exports.computeCodeChallenge = computeCodeChallenge;
|
|
22
22
|
exports.generatePkcePair = generatePkcePair;
|
|
23
23
|
exports.generateOAuthState = generateOAuthState;
|
|
24
24
|
exports.buildOAuthAuthorizeUrl = buildOAuthAuthorizeUrl;
|
|
25
|
+
exports.normalizeOAuthRedirectUri = normalizeOAuthRedirectUri;
|
|
26
|
+
exports.persistOAuthHandshake = persistOAuthHandshake;
|
|
27
|
+
exports.readOAuthHandshake = readOAuthHandshake;
|
|
28
|
+
exports.clearOAuthHandshake = clearOAuthHandshake;
|
|
25
29
|
const protocol_1 = require("@oxyhq/protocol");
|
|
26
30
|
const loggerUtils_1 = require("./loggerUtils");
|
|
27
31
|
/** The central Oxy IdP authorization endpoint used by default. */
|
|
@@ -138,5 +142,65 @@ function buildOAuthAuthorizeUrl(params) {
|
|
|
138
142
|
url.searchParams.set('scope', scope);
|
|
139
143
|
url.searchParams.set('code_challenge', codeChallenge);
|
|
140
144
|
url.searchParams.set('code_challenge_method', 'S256');
|
|
145
|
+
if (params.prompt) {
|
|
146
|
+
url.searchParams.set('prompt', params.prompt);
|
|
147
|
+
}
|
|
141
148
|
return url.toString();
|
|
142
149
|
}
|
|
150
|
+
/** `sessionStorage` key for the OAuth CSRF `state` across an authorize redirect. */
|
|
151
|
+
exports.OXY_OAUTH_STATE_STORAGE_KEY = 'oxy_oauth_state';
|
|
152
|
+
/** `sessionStorage` key for the PKCE `code_verifier` across an authorize redirect. */
|
|
153
|
+
exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = 'oxy_oauth_code_verifier';
|
|
154
|
+
/** `sessionStorage` key — at most one silent OAuth attempt per navigation. */
|
|
155
|
+
exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = 'oxy.silent_oauth_attempted';
|
|
156
|
+
/** `sessionStorage` key — blocks further cross-origin auto-restore in this tab. */
|
|
157
|
+
exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = 'oxy.cross_origin_restore_attempted';
|
|
158
|
+
/**
|
|
159
|
+
* Normalize a redirect URI to its origin. Official Oxy apps register apex
|
|
160
|
+
* origins (`https://inbox.oxy.so`) — never path-qualified URLs.
|
|
161
|
+
*/
|
|
162
|
+
function normalizeOAuthRedirectUri(input) {
|
|
163
|
+
try {
|
|
164
|
+
return new URL(input).origin;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return input;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Persist the OAuth handshake for a full-page redirect return (web only). */
|
|
171
|
+
function persistOAuthHandshake(state, codeVerifier) {
|
|
172
|
+
const store = globalThis.sessionStorage;
|
|
173
|
+
try {
|
|
174
|
+
if (!store)
|
|
175
|
+
throw new Error('sessionStorage is unavailable');
|
|
176
|
+
store.setItem(exports.OXY_OAUTH_STATE_STORAGE_KEY, state);
|
|
177
|
+
store.setItem(exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY, codeVerifier);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
loggerUtils_1.logger.warn('Could not persist OAuth handshake to sessionStorage', { component: 'oauthPkce' }, error);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Read the persisted OAuth handshake, or `null` when absent. */
|
|
186
|
+
function readOAuthHandshake() {
|
|
187
|
+
const store = globalThis.sessionStorage;
|
|
188
|
+
if (!store)
|
|
189
|
+
return null;
|
|
190
|
+
const state = store.getItem(exports.OXY_OAUTH_STATE_STORAGE_KEY);
|
|
191
|
+
const codeVerifier = store.getItem(exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY);
|
|
192
|
+
if (!state || !codeVerifier)
|
|
193
|
+
return null;
|
|
194
|
+
return { state, codeVerifier };
|
|
195
|
+
}
|
|
196
|
+
/** Drop persisted OAuth handshake keys after a successful or aborted return. */
|
|
197
|
+
function clearOAuthHandshake() {
|
|
198
|
+
const store = globalThis.sessionStorage;
|
|
199
|
+
try {
|
|
200
|
+
store?.removeItem(exports.OXY_OAUTH_STATE_STORAGE_KEY);
|
|
201
|
+
store?.removeItem(exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Best-effort cleanup only.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Official first-party web origin allowlist — shared by hub-ticket issuance,
|
|
4
|
+
* OAuth redirect validation, and cross-origin session restore.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isAllowedDeviceJoinOrigin = void 0;
|
|
8
|
+
exports.buildIdpHubOrigin = buildIdpHubOrigin;
|
|
9
|
+
exports.isIdpHubOrigin = isIdpHubOrigin;
|
|
10
|
+
exports.isOfficialWebOrigin = isOfficialWebOrigin;
|
|
11
|
+
exports.normalizeOfficialReturnOrigin = normalizeOfficialReturnOrigin;
|
|
12
|
+
exports.parseHubSyncReturnUrl = parseHubSyncReturnUrl;
|
|
13
|
+
exports.buildHubSyncUrl = buildHubSyncUrl;
|
|
14
|
+
const authWebUrl_1 = require("./authWebUrl");
|
|
15
|
+
const registrableApex_1 = require("./registrableApex");
|
|
16
|
+
/** Official first-party registrable apexes (mirrors API trusted origins). */
|
|
17
|
+
const OFFICIAL_APEXES = new Set([
|
|
18
|
+
'oxy.so',
|
|
19
|
+
'fairco.in',
|
|
20
|
+
'mention.earth',
|
|
21
|
+
'homiio.com',
|
|
22
|
+
'alia.onl',
|
|
23
|
+
'syra.fm',
|
|
24
|
+
'allo.you',
|
|
25
|
+
'tnp.network',
|
|
26
|
+
'moovo.now',
|
|
27
|
+
'mercaria.co',
|
|
28
|
+
]);
|
|
29
|
+
function buildIdpHubOrigin() {
|
|
30
|
+
return `https://auth.${authWebUrl_1.CENTRAL_IDP_APEX}`;
|
|
31
|
+
}
|
|
32
|
+
/** Whether the current web origin is the central IdP hub (`auth.oxy.so`). */
|
|
33
|
+
function isIdpHubOrigin() {
|
|
34
|
+
if (typeof globalThis === 'undefined') {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const location = globalThis.location;
|
|
38
|
+
if (!location) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const { hostname } = new URL(location.href);
|
|
43
|
+
return hostname === `auth.${authWebUrl_1.CENTRAL_IDP_APEX}`;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isLoopbackOrigin(origin) {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = new URL(origin);
|
|
52
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const host = parsed.hostname.toLowerCase();
|
|
56
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '[::1]';
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Whether an origin belongs to the official Oxy web ecosystem. */
|
|
63
|
+
function isOfficialWebOrigin(origin) {
|
|
64
|
+
if (isLoopbackOrigin(origin)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const parsed = new URL(origin);
|
|
69
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const host = parsed.hostname.toLowerCase();
|
|
73
|
+
if (host === authWebUrl_1.CENTRAL_IDP_APEX || host.endsWith(`.${authWebUrl_1.CENTRAL_IDP_APEX}`)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const apex = (0, registrableApex_1.registrableApex)(host);
|
|
77
|
+
return apex != null && OFFICIAL_APEXES.has(apex);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Normalize and validate a return URL against official origins. Returns origin only. */
|
|
84
|
+
function normalizeOfficialReturnOrigin(raw) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = new URL(raw);
|
|
87
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (!isOfficialWebOrigin(parsed.origin)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return parsed.origin;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Validate a hub-sync return URL; returns the full normalized URL string. */
|
|
100
|
+
function parseHubSyncReturnUrl(raw) {
|
|
101
|
+
if (!raw) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const parsed = new URL(raw);
|
|
106
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (!isOfficialWebOrigin(parsed.origin)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return parsed.toString();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Build auth.oxy.so/sync redirect URL with a one-time hub ticket. */
|
|
119
|
+
function buildHubSyncUrl(ticket, returnUrl) {
|
|
120
|
+
const url = new URL('/sync', buildIdpHubOrigin());
|
|
121
|
+
url.searchParams.set('ticket', ticket);
|
|
122
|
+
if (returnUrl) {
|
|
123
|
+
url.searchParams.set('return', returnUrl);
|
|
124
|
+
}
|
|
125
|
+
return url.toString();
|
|
126
|
+
}
|
|
127
|
+
/** @deprecated Use {@link isOfficialWebOrigin}. */
|
|
128
|
+
exports.isAllowedDeviceJoinOrigin = isOfficialWebOrigin;
|