@oxyhq/core 3.2.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/AuthManager.js +3 -1
- package/dist/cjs/HttpService.js +89 -0
- package/dist/cjs/OxyServices.js +1 -1
- package/dist/cjs/constants/version.js +1 -1
- package/dist/cjs/i18n/locales/en-US.json +44 -44
- package/dist/cjs/i18n/locales/es-ES.json +44 -44
- package/dist/cjs/i18n/locales/locales/en-US.json +44 -44
- package/dist/cjs/i18n/locales/locales/es-ES.json +44 -44
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/mixins/OxyServices.applications.js +3 -1
- package/dist/cjs/mixins/OxyServices.reputation.js +244 -0
- package/dist/cjs/mixins/OxyServices.workspaces.js +3 -1
- package/dist/cjs/mixins/index.js +2 -2
- package/dist/cjs/utils/accountUtils.js +12 -5
- package/dist/cjs/utils/ssoReturn.js +80 -33
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +3 -1
- package/dist/esm/HttpService.js +89 -0
- package/dist/esm/OxyServices.js +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/i18n/locales/en-US.json +44 -44
- package/dist/esm/i18n/locales/es-ES.json +44 -44
- package/dist/esm/i18n/locales/locales/en-US.json +44 -44
- package/dist/esm/i18n/locales/locales/es-ES.json +44 -44
- package/dist/esm/index.js +4 -0
- package/dist/esm/mixins/OxyServices.applications.js +3 -1
- package/dist/esm/mixins/OxyServices.reputation.js +241 -0
- package/dist/esm/mixins/OxyServices.workspaces.js +3 -1
- package/dist/esm/mixins/index.js +2 -2
- package/dist/esm/utils/accountUtils.js +12 -5
- package/dist/esm/utils/ssoReturn.js +80 -33
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +57 -0
- package/dist/types/OxyServices.d.ts +1 -1
- package/dist/types/constants/version.d.ts +2 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +8 -2
- package/dist/types/mixins/OxyServices.features.d.ts +0 -1
- package/dist/types/mixins/OxyServices.reputation.d.ts +436 -0
- package/dist/types/mixins/OxyServices.workspaces.d.ts +8 -2
- package/dist/types/mixins/index.d.ts +2 -2
- package/dist/types/models/interfaces.d.ts +15 -26
- package/dist/types/utils/accountUtils.d.ts +17 -4
- package/dist/types/utils/ssoReturn.d.ts +30 -9
- package/package.json +2 -1
- package/src/AuthManager.ts +3 -1
- package/src/HttpService.ts +91 -0
- package/src/OxyServices.ts +1 -1
- package/src/__tests__/httpServiceCache.test.ts +198 -0
- package/src/constants/version.ts +1 -1
- package/src/i18n/locales/en-US.json +44 -44
- package/src/i18n/locales/es-ES.json +44 -44
- package/src/index.ts +32 -4
- package/src/mixins/OxyServices.applications.ts +8 -2
- package/src/mixins/OxyServices.auth.ts +2 -1
- package/src/mixins/OxyServices.features.ts +0 -1
- package/src/mixins/OxyServices.reputation.ts +674 -0
- package/src/mixins/OxyServices.workspaces.ts +8 -2
- package/src/mixins/__tests__/reputation.test.ts +408 -0
- package/src/mixins/index.ts +3 -3
- package/src/models/interfaces.ts +16 -32
- package/src/utils/__tests__/accountUtils.test.ts +142 -0
- package/src/utils/__tests__/consumeSsoReturn.test.ts +229 -37
- package/src/utils/accountUtils.ts +20 -5
- package/src/utils/ssoReturn.ts +98 -37
- package/dist/cjs/mixins/OxyServices.developer.js +0 -97
- package/dist/cjs/mixins/OxyServices.karma.js +0 -108
- package/dist/esm/mixins/OxyServices.developer.js +0 -94
- package/dist/esm/mixins/OxyServices.karma.js +0 -105
- package/dist/types/mixins/OxyServices.developer.d.ts +0 -106
- package/dist/types/mixins/OxyServices.karma.d.ts +0 -92
- package/src/mixins/OxyServices.karma.ts +0 -111
|
@@ -84,7 +84,9 @@ export function OxyServicesWorkspacesMixin(Base) {
|
|
|
84
84
|
/**
|
|
85
85
|
* Add a member to a workspace.
|
|
86
86
|
* @param workspaceId - The workspace's Mongo `_id`.
|
|
87
|
-
* @param data - Target user
|
|
87
|
+
* @param data - Target user's username or email and role (never `owner`).
|
|
88
|
+
* The server resolves `usernameOrEmail` to a user; an unknown value yields
|
|
89
|
+
* a 404 "User not found".
|
|
88
90
|
*/
|
|
89
91
|
async inviteWorkspaceMember(workspaceId, data) {
|
|
90
92
|
try {
|
package/dist/esm/mixins/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { OxyServicesUserMixin } from './OxyServices.user.js';
|
|
|
14
14
|
import { OxyServicesPrivacyMixin } from './OxyServices.privacy.js';
|
|
15
15
|
import { OxyServicesLanguageMixin } from './OxyServices.language.js';
|
|
16
16
|
import { OxyServicesPaymentMixin } from './OxyServices.payment.js';
|
|
17
|
-
import {
|
|
17
|
+
import { OxyServicesReputationMixin } from './OxyServices.reputation.js';
|
|
18
18
|
import { OxyServicesAssetsMixin } from './OxyServices.assets.js';
|
|
19
19
|
import { OxyServicesApplicationsMixin } from './OxyServices.applications.js';
|
|
20
20
|
import { OxyServicesWorkspacesMixin } from './OxyServices.workspaces.js';
|
|
@@ -59,7 +59,7 @@ const MIXIN_PIPELINE = [
|
|
|
59
59
|
// Feature mixins
|
|
60
60
|
OxyServicesLanguageMixin,
|
|
61
61
|
OxyServicesPaymentMixin,
|
|
62
|
-
|
|
62
|
+
OxyServicesReputationMixin,
|
|
63
63
|
OxyServicesAssetsMixin,
|
|
64
64
|
OxyServicesApplicationsMixin,
|
|
65
65
|
OxyServicesWorkspacesMixin,
|
|
@@ -17,11 +17,16 @@ export const formatPublicKeyHandle = (publicKey) => {
|
|
|
17
17
|
* Resolve a friendly display name for a user.
|
|
18
18
|
*
|
|
19
19
|
* Order of preference:
|
|
20
|
-
* 1. `name.full`, or composed `name.first name.last`
|
|
20
|
+
* 1. `name.full`, or composed `name.first name.last` (FIRST-NAME-ONLY SAFE —
|
|
21
|
+
* a user with only a first name resolves to that first name, never to the
|
|
22
|
+
* lowercase username; this is the exact drift bug the auth app hit).
|
|
21
23
|
* 2. `name` (when stored as a plain string)
|
|
22
|
-
* 3. `username`
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* 3. `displayName` (server `displayName` virtual — `username || truncatedKey`).
|
|
25
|
+
* Placed AFTER the structured name on purpose: the server virtual ignores
|
|
26
|
+
* `name`, so preferring it first would re-introduce the first-only bug.
|
|
27
|
+
* 4. `username`
|
|
28
|
+
* 5. `Account 0x12345678…` (derived from publicKey, when present)
|
|
29
|
+
* 6. Translated fallback (e.g. "Unnamed")
|
|
25
30
|
*
|
|
26
31
|
* The translation key `common.unnamed` is used for the final fallback. If the
|
|
27
32
|
* caller does not pass a locale, the default English translation is used.
|
|
@@ -29,7 +34,7 @@ export const formatPublicKeyHandle = (publicKey) => {
|
|
|
29
34
|
export const getAccountDisplayName = (user, locale) => {
|
|
30
35
|
if (!user)
|
|
31
36
|
return translate(locale, 'common.unnamed');
|
|
32
|
-
const { name, username, publicKey } = user;
|
|
37
|
+
const { name, displayName, username, publicKey } = user;
|
|
33
38
|
if (name && typeof name === 'object') {
|
|
34
39
|
if (typeof name.full === 'string' && name.full.trim())
|
|
35
40
|
return name.full.trim();
|
|
@@ -42,6 +47,8 @@ export const getAccountDisplayName = (user, locale) => {
|
|
|
42
47
|
else if (typeof name === 'string' && name.trim()) {
|
|
43
48
|
return name.trim();
|
|
44
49
|
}
|
|
50
|
+
if (typeof displayName === 'string' && displayName.trim())
|
|
51
|
+
return displayName.trim();
|
|
45
52
|
if (typeof username === 'string' && username.trim())
|
|
46
53
|
return username.trim();
|
|
47
54
|
if (typeof publicKey === 'string' && publicKey.length > 0) {
|
|
@@ -92,14 +92,22 @@ export function parseSsoReturnFragment(hash) {
|
|
|
92
92
|
* treated exactly like "no session" (never loops, never rethrows).
|
|
93
93
|
* - On EVERY consumed outcome (ok, none, error, state-mismatch, no-code,
|
|
94
94
|
* failed-exchange, no-sessionId) — not just ok — if the page landed on
|
|
95
|
-
* {@link SSO_CALLBACK_PATH}, the
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
95
|
+
* {@link SSO_CALLBACK_PATH}, the user is taken to a same-origin TARGET so
|
|
96
|
+
* they are never stranded on the internal callback path (which is an
|
|
97
|
+
* unregistered route in every consumer router → a hard 404). The target is
|
|
98
|
+
* the stored DEST when it parses as same-origin (an attacker-planted
|
|
99
|
+
* cross-origin / protocol-relative dest is rejected), ELSE the app root
|
|
100
|
+
* (`origin + '/'`). The DEST key is removed unconditionally.
|
|
101
|
+
* - For the `ok` outcome the target is applied via a SOFT
|
|
102
|
+
* `history.replaceState` + synthetic `popstate` so the freshly exchanged
|
|
103
|
+
* in-memory session the provider is about to commit is preserved (no
|
|
104
|
+
* reload). `popstate` is dispatched only on the `ok` same-origin restore.
|
|
105
|
+
* - For every NON-`ok` outcome there is no in-memory session to preserve, and
|
|
106
|
+
* the consumer router has ALREADY synchronously rendered its 404 for the
|
|
107
|
+
* unregistered callback route — a soft replaceState+popstate does not
|
|
108
|
+
* reliably make it re-resolve. So these outcomes perform a HARD
|
|
109
|
+
* full-document navigation to the target (`hardRedirect`), which is both
|
|
110
|
+
* safe (nothing to lose) and guaranteed to clear the 404 in every router.
|
|
103
111
|
*
|
|
104
112
|
* Total: this function NEVER throws. Off-web it is a no-op returning `null`.
|
|
105
113
|
*
|
|
@@ -133,6 +141,17 @@ export async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
133
141
|
window.dispatchEvent(new Event('popstate'));
|
|
134
142
|
}
|
|
135
143
|
});
|
|
144
|
+
// Default: a hard, full-document navigation used to leave the callback path
|
|
145
|
+
// on non-`ok` outcomes. Feature-detected end to end so it never throws in any
|
|
146
|
+
// environment (SSR / native / a stubbed location without `replace`).
|
|
147
|
+
const hardRedirect = deps.hardRedirect ??
|
|
148
|
+
((url) => {
|
|
149
|
+
if (typeof window !== 'undefined' &&
|
|
150
|
+
window.location &&
|
|
151
|
+
typeof window.location.replace === 'function') {
|
|
152
|
+
window.location.replace(url);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
136
155
|
const ret = parseSsoReturnFragment(location.hash);
|
|
137
156
|
if (!ret) {
|
|
138
157
|
// Not an oxy_sso fragment — nothing to do (do NOT touch any flags).
|
|
@@ -155,42 +174,62 @@ export async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
155
174
|
// even if some consumer path skipped setting it pre-bounce.
|
|
156
175
|
storage.setItem(ssoAttemptedKey(origin), '1');
|
|
157
176
|
};
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
// Compute the same-origin TARGET to leave the callback path for. Returns the
|
|
178
|
+
// stored DEST when present AND it parses as same-origin (never honour a
|
|
179
|
+
// cross-origin / protocol-relative dest that could have been planted to
|
|
180
|
+
// redirect the user), ELSE the app root (`origin + '/'`) so the user is never
|
|
181
|
+
// stranded on the internal callback path even when no dest was stored. The
|
|
182
|
+
// DEST key is removed unconditionally. Returns the relative path+search+hash
|
|
183
|
+
// (so it can be fed to either `history.replaceState` or a `hardRedirect`),
|
|
184
|
+
// or `null` when the page is not on the callback path (nothing to leave).
|
|
185
|
+
const consumeCallbackTarget = () => {
|
|
186
|
+
if (location.pathname !== SSO_CALLBACK_PATH) {
|
|
187
|
+
// Not on the callback path — still drop the dest key (consumed) but there
|
|
188
|
+
// is nothing to navigate away from.
|
|
189
|
+
storage.removeItem(ssoDestKey(origin));
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const dest = storage.getItem(ssoDestKey(origin));
|
|
193
|
+
storage.removeItem(ssoDestKey(origin));
|
|
194
|
+
if (dest) {
|
|
195
|
+
try {
|
|
196
|
+
const destUrl = new URL(dest, origin);
|
|
197
|
+
if (destUrl.origin === origin) {
|
|
198
|
+
return destUrl.pathname + destUrl.search + destUrl.hash;
|
|
177
199
|
}
|
|
178
200
|
}
|
|
201
|
+
catch {
|
|
202
|
+
// Malformed stored destination — fall through to the app-root fallback.
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// No dest, a cross-origin/protocol-relative dest, or an unparseable dest:
|
|
206
|
+
// fall back to the app root so the router always leaves the 404.
|
|
207
|
+
return '/';
|
|
208
|
+
};
|
|
209
|
+
// Non-`ok` outcomes: there is no in-memory session to preserve, and the
|
|
210
|
+
// consumer router has already rendered its 404 for the unregistered callback
|
|
211
|
+
// route — a soft replaceState+popstate does not reliably make it re-resolve.
|
|
212
|
+
// Perform a HARD full-document navigation to the target (safe: nothing to
|
|
213
|
+
// lose; guaranteed: every router leaves the 404). Off the callback path this
|
|
214
|
+
// is a no-op (target is null).
|
|
215
|
+
const leaveCallbackHard = () => {
|
|
216
|
+
const target = consumeCallbackTarget();
|
|
217
|
+
if (target !== null) {
|
|
218
|
+
hardRedirect(origin + target);
|
|
179
219
|
}
|
|
180
|
-
storage.removeItem(ssoDestKey(origin));
|
|
181
220
|
};
|
|
182
221
|
if (ret.kind === 'none' || ret.kind === 'error') {
|
|
183
222
|
// The central IdP had no session (or the bounce failed). Record it so we do
|
|
184
223
|
// not bounce again this tab — the definitive loop breaker.
|
|
185
224
|
markNoSession();
|
|
186
|
-
|
|
225
|
+
leaveCallbackHard();
|
|
187
226
|
return null;
|
|
188
227
|
}
|
|
189
228
|
if (!stateOk || !ret.code) {
|
|
190
229
|
// Forged / replayed / stale fragment, or a malformed ok with no code. Treat
|
|
191
230
|
// exactly like "no session": never exchange, never loop.
|
|
192
231
|
markNoSession();
|
|
193
|
-
|
|
232
|
+
leaveCallbackHard();
|
|
194
233
|
return null;
|
|
195
234
|
}
|
|
196
235
|
let session;
|
|
@@ -200,14 +239,22 @@ export async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
200
239
|
catch (error) {
|
|
201
240
|
onExchangeError?.(error);
|
|
202
241
|
markNoSession();
|
|
203
|
-
|
|
242
|
+
leaveCallbackHard();
|
|
204
243
|
return null;
|
|
205
244
|
}
|
|
206
245
|
if (!session?.sessionId) {
|
|
207
246
|
markNoSession();
|
|
208
|
-
|
|
247
|
+
leaveCallbackHard();
|
|
209
248
|
return null;
|
|
210
249
|
}
|
|
211
|
-
|
|
250
|
+
// `ok`: the provider is about to commit the freshly exchanged in-memory
|
|
251
|
+
// session — do NOT hard-redirect (a full navigation would discard it). Use a
|
|
252
|
+
// SOFT `history.replaceState` to the target + a synthetic `popstate` so
|
|
253
|
+
// URL-driven routers re-sync to the restored route without a reload.
|
|
254
|
+
const target = consumeCallbackTarget();
|
|
255
|
+
if (target !== null) {
|
|
256
|
+
history.replaceState(null, '', target);
|
|
257
|
+
dispatchPopState();
|
|
258
|
+
}
|
|
212
259
|
return session;
|
|
213
260
|
}
|