@oxyhq/core 6.0.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 +1 -1
- package/dist/cjs/index.js +17 -11
- package/dist/cjs/mixins/OxyServices.accounts.js +7 -13
- package/dist/cjs/mixins/index.js +0 -5
- package/dist/cjs/server/index.js +2 -2
- package/dist/cjs/shared/utils/debugUtils.js +3 -3
- package/dist/cjs/utils/authWebUrl.js +10 -30
- package/dist/cjs/utils/coldBoot.js +10 -8
- package/dist/cjs/utils/{fapiAutoDetect.js → registrableApex.js} +8 -10
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +1 -1
- package/dist/esm/index.js +15 -8
- package/dist/esm/mixins/OxyServices.accounts.js +7 -13
- package/dist/esm/mixins/index.js +0 -5
- package/dist/esm/server/index.js +1 -1
- package/dist/esm/shared/utils/debugUtils.js +3 -3
- package/dist/esm/utils/authWebUrl.js +9 -29
- package/dist/esm/utils/coldBoot.js +10 -8
- package/dist/esm/utils/{fapiAutoDetect.js → registrableApex.js} +8 -10
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +1 -1
- package/dist/types/index.d.ts +2 -3
- package/dist/types/mixins/OxyServices.accounts.d.ts +24 -27
- package/dist/types/mixins/index.d.ts +1 -2
- 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 +1 -1
- package/dist/types/shared/utils/debugUtils.d.ts +3 -3
- package/dist/types/utils/accountUtils.d.ts +2 -3
- package/dist/types/utils/authWebUrl.d.ts +9 -29
- package/dist/types/utils/coldBoot.d.ts +17 -14
- package/dist/types/utils/{fapiAutoDetect.d.ts → registrableApex.d.ts} +8 -10
- package/package.json +2 -2
- package/src/HttpService.ts +1 -1
- package/src/index.ts +12 -11
- package/src/mixins/OxyServices.accounts.ts +21 -26
- package/src/mixins/index.ts +0 -7
- package/src/models/session.ts +4 -5
- package/src/server/index.ts +1 -1
- package/src/session/SessionClient.ts +1 -1
- package/src/shared/utils/debugUtils.ts +3 -3
- package/src/utils/__tests__/authWebUrl.test.ts +5 -16
- package/src/utils/__tests__/{fapiAutoDetect.test.ts → registrableApex.test.ts} +1 -1
- package/src/utils/accountUtils.ts +2 -3
- package/src/utils/authWebUrl.ts +9 -30
- package/src/utils/coldBoot.ts +17 -14
- package/src/utils/{fapiAutoDetect.ts → registrableApex.ts} +8 -10
- package/dist/cjs/mixins/OxyServices.authorizedApps.js +0 -38
- package/dist/esm/mixins/OxyServices.authorizedApps.js +0 -35
- package/dist/types/mixins/OxyServices.authorizedApps.d.ts +0 -94
- package/src/mixins/OxyServices.authorizedApps.ts +0 -75
- package/src/mixins/__tests__/authorizedApps.test.ts +0 -63
|
@@ -157,7 +157,7 @@ export declare class HttpService {
|
|
|
157
157
|
* `clearCacheByPrefix` sweeps and `clearCacheEntry` base-key matching.
|
|
158
158
|
* The `clearCacheEntry` callsites all pass fixed, dataless logical keys
|
|
159
159
|
* (`GET:/users/<id>`, `GET:/session/user/<sessionId>`,
|
|
160
|
-
* `GET:/
|
|
160
|
+
* `GET:/auth/grants`), so this readable suffix can never be
|
|
161
161
|
* ambiguous with a serialized request body.
|
|
162
162
|
*/
|
|
163
163
|
private static readonly CACHE_IDENTITY_DELIM;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -21,7 +21,6 @@ export { OxyServices, OxyAuthenticationError, OxyAuthenticationTimeoutError } fr
|
|
|
21
21
|
export { OXY_CLOUD_URL, oxyClient } from './OxyServices';
|
|
22
22
|
export type { LinkedHttpClient } from './OxyServices.base';
|
|
23
23
|
export type { AuthRefreshReason, AuthRefreshHandler } from './HttpService';
|
|
24
|
-
export type { AuthorizedApp } from './mixins/OxyServices.authorizedApps';
|
|
25
24
|
export { ServiceCredentialMismatchError } from './mixins/OxyServices.auth';
|
|
26
25
|
export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
|
|
27
26
|
export type { CommonsSignInHandle, CommonsSignInStatus, CommonsApprovalInfo, CommonsSignInActionResult, } from './mixins/OxyServices.auth';
|
|
@@ -80,8 +79,8 @@ export type { LogContext } from './utils/loggerUtils';
|
|
|
80
79
|
export { updateAvatarVisibility } from './utils/avatarUtils';
|
|
81
80
|
export { buildAccountsArray, createQuickAccount, getAccountDisplayName, getAccountFallbackHandle, formatPublicKeyHandle, getAccountColor, } from './utils/accountUtils';
|
|
82
81
|
export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
|
|
83
|
-
export { registrableApex } from './utils/
|
|
84
|
-
export {
|
|
82
|
+
export { registrableApex } from './utils/registrableApex';
|
|
83
|
+
export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
|
|
85
84
|
export { SSO_CALLBACK_PATH } from './utils/ssoBounce';
|
|
86
85
|
export { runColdBoot } from './utils/coldBoot';
|
|
87
86
|
export type { ColdBootStep, ColdBootStepResult, ColdBootSession, ColdBootSkip, ColdBootOutcome, RunColdBootOptions, } from './utils/coldBoot';
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
* SWITCHING INTO AN ACCOUNT: `switchToAccount(accountId)` mints a REAL session
|
|
29
29
|
* for the target account and plants it as the active session — there is no
|
|
30
30
|
* per-request "acting-as" header. Identity is carried by the session/token, not
|
|
31
|
-
* a delegation header, so a switch propagates through reload and
|
|
32
|
-
* exactly like a login
|
|
31
|
+
* a delegation header, so a switch propagates through reload and cross-domain
|
|
32
|
+
* exactly like a login, via the device-first session model (the server
|
|
33
|
+
* registers the switched session into the operator's device-set directly).
|
|
33
34
|
*/
|
|
34
35
|
import type { User } from '../models/interfaces';
|
|
35
36
|
import type { SessionLoginResponse } from '../models/session';
|
|
@@ -387,22 +388,22 @@ export interface AccountSuccessResult {
|
|
|
387
388
|
/**
|
|
388
389
|
* Result of {@link OxyServicesAccountsMixin.switchToAccount} — the freshly
|
|
389
390
|
* minted session for the target account, in the SAME shape the canonical login
|
|
390
|
-
* / `claimSessionByToken` responses use (`SessionLoginResponse`)
|
|
391
|
-
* device-local refresh-cookie slot index.
|
|
391
|
+
* / `claimSessionByToken` responses use (`SessionLoginResponse`).
|
|
392
392
|
*
|
|
393
393
|
* `accessToken` is the first access token for the new session (already planted
|
|
394
|
-
* as the active token by `switchToAccount`). The
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
394
|
+
* as the active token by `switchToAccount`). The switched session's survival
|
|
395
|
+
* across reload and cross-domain sync is device-first: the server registers
|
|
396
|
+
* it into the operator's `DeviceSession` set directly
|
|
397
|
+
* (`deviceSessionService.addAccount`, broadcast to the device room) — there is
|
|
398
|
+
* no client-side refresh-cookie slot to establish. `user` is the target
|
|
399
|
+
* account.
|
|
398
400
|
*/
|
|
399
401
|
export interface SwitchAccountResult extends SessionLoginResponse {
|
|
400
402
|
/**
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* switch itself still succeeds).
|
|
403
|
+
* Legacy device-local refresh-cookie slot index. The current server switch
|
|
404
|
+
* response never sets this field (device-set registration replaced the
|
|
405
|
+
* cookie-slot model) — kept optional for backward type-compatibility with
|
|
406
|
+
* any caller still reading it, but always `undefined` in practice.
|
|
406
407
|
*/
|
|
407
408
|
authuser?: number;
|
|
408
409
|
}
|
|
@@ -432,24 +433,18 @@ export declare function OxyServicesAccountsMixin<T extends typeof OxyServicesBas
|
|
|
432
433
|
* target, directly or inherited — else 403; 404 if missing/archived; 403 if
|
|
433
434
|
* the target is a personal account), then mints a REAL session for the
|
|
434
435
|
* target account and returns it in the canonical login / `claimSessionByToken`
|
|
435
|
-
* shape (`{ sessionId, deviceId, expiresAt, accessToken, user
|
|
436
|
+
* shape (`{ sessionId, deviceId, expiresAt, accessToken, user }`).
|
|
436
437
|
*
|
|
437
438
|
* Unlike the removed `X-Acting-As` delegation header, the returned session
|
|
438
439
|
* IS the new identity: this plants `accessToken` as the active token —
|
|
439
440
|
* exactly like `claimSessionByToken` / `verifyChallenge` — so every
|
|
440
441
|
* subsequent request authenticates as the target account.
|
|
441
442
|
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
* would clobber slot 0 (destroying the operator's own session). `/auth/session`
|
|
448
|
-
* runs where those cookies ARE visible, so the server allocates a NEW slot that
|
|
449
|
-
* coexists with the operator's and returns its `authuser`. This step is
|
|
450
|
-
* web-only (native multi-account uses stored sessions, not cookies) and
|
|
451
|
-
* best-effort — a failure leaves the in-session switch intact; the switched
|
|
452
|
-
* account simply won't survive a reload until the cookie is next established.
|
|
443
|
+
* A single call is all that's needed: the server registers the switched
|
|
444
|
+
* session into the operator's `DeviceSession` set directly, inheriting the
|
|
445
|
+
* operator's central `deviceId` so the switch survives a reload and syncs
|
|
446
|
+
* cross-domain via the same device-first session model as a normal login —
|
|
447
|
+
* there is no separate client-side cookie/slot step to make it stick.
|
|
453
448
|
*
|
|
454
449
|
* After planting, the SDK's identity-scoped GET cache is fully cleared so
|
|
455
450
|
* every cached read re-fetches as the new account. (The consuming
|
|
@@ -459,7 +454,7 @@ export declare function OxyServicesAccountsMixin<T extends typeof OxyServicesBas
|
|
|
459
454
|
* same-user silent refreshes, so the sweep here is explicit.)
|
|
460
455
|
*
|
|
461
456
|
* @param accountId - The target account's Mongo `_id`.
|
|
462
|
-
* @returns The minted session
|
|
457
|
+
* @returns The minted session, already planted as the active session.
|
|
463
458
|
*/
|
|
464
459
|
switchToAccount(accountId: string): Promise<SwitchAccountResult>;
|
|
465
460
|
/**
|
|
@@ -694,7 +689,9 @@ export declare function OxyServicesAccountsMixin<T extends typeof OxyServicesBas
|
|
|
694
689
|
handleError(error: unknown): Error;
|
|
695
690
|
healthCheck(): Promise<{
|
|
696
691
|
status: string;
|
|
697
|
-
users
|
|
692
|
+
users
|
|
693
|
+
/** Aggregate totals for an application over the requested period. */
|
|
694
|
+
? /** Aggregate totals for an application over the requested period. */: number;
|
|
698
695
|
timestamp?: string;
|
|
699
696
|
[key: string]: any;
|
|
700
697
|
}>;
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { OxyServicesBase } from '../OxyServices.base';
|
|
8
8
|
import { OxyServicesAuthMixin } from './OxyServices.auth';
|
|
9
|
-
import { OxyServicesAuthorizedAppsMixin } from './OxyServices.authorizedApps';
|
|
10
9
|
import { OxyServicesUserMixin } from './OxyServices.user';
|
|
11
10
|
import { OxyServicesIdentityMixin } from './OxyServices.identity';
|
|
12
11
|
import { OxyServicesPrivacyMixin } from './OxyServices.privacy';
|
|
@@ -38,7 +37,7 @@ import { OxyServicesDeviceBootMixin } from './OxyServices.deviceBoot';
|
|
|
38
37
|
* If you add a new mixin to `MIXIN_PIPELINE`, add it here too so its methods
|
|
39
38
|
* are visible without a cast.
|
|
40
39
|
*/
|
|
41
|
-
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof
|
|
40
|
+
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesConnectedAppsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNodesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLinksMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDeviceBootMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
42
41
|
/**
|
|
43
42
|
* Constructor type for the fully composed mixin pipeline. Each mixin returns
|
|
44
43
|
* a new constructor that augments its input; reducing across the pipeline
|
|
@@ -7,11 +7,10 @@ export interface ClientSession {
|
|
|
7
7
|
userId?: string;
|
|
8
8
|
isCurrent?: boolean;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Absent on native (RN uses the bearer-protected session id directly).
|
|
10
|
+
* The account's ordinal slot (0..N) within the device's account set
|
|
11
|
+
* (`SessionAccount.authuser` in `@oxyhq/contracts`), projected from the
|
|
12
|
+
* device-first `DeviceSessionState` — used purely for stable Google-style
|
|
13
|
+
* account-chooser ordering, not for any token-refresh mechanism.
|
|
15
14
|
*/
|
|
16
15
|
authuser?: number;
|
|
17
16
|
}
|
|
@@ -23,5 +23,5 @@ export type { SafeFetchOptions, SafeFetchResult, SsrfCheckFail, SsrfCheckOk, Ssr
|
|
|
23
23
|
export { createOxyCors } from './cors';
|
|
24
24
|
export type { OxyCorsOptions } from './cors';
|
|
25
25
|
export { verifySecret } from './verifySecret';
|
|
26
|
-
export { registrableApex } from '../utils/
|
|
26
|
+
export { registrableApex } from '../utils/registrableApex';
|
|
27
27
|
export { SSO_CALLBACK_PATH } from '../utils/ssoBounce';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type DeviceSessionState } from '@oxyhq/contracts';
|
|
2
2
|
import type { MinimalSocket, SocketIOFactory } from './socketLoader';
|
|
3
3
|
export interface TokenTransport {
|
|
4
|
-
/** Ensure this app holds a per-domain access token for state.activeAccountId (mint via
|
|
4
|
+
/** Ensure this app holds a per-domain access token for state.activeAccountId (mint via the persisted refresh family / shared keychain). Best-effort. */
|
|
5
5
|
ensureActiveToken(state: DeviceSessionState): Promise<void>;
|
|
6
6
|
}
|
|
7
7
|
export interface SessionClientHost {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
export declare const isDev: () => boolean;
|
|
13
13
|
/**
|
|
14
14
|
* Log a debug message (only in development)
|
|
15
|
-
* @param prefix - Log prefix (e.g., '[
|
|
15
|
+
* @param prefix - Log prefix (e.g., '[ColdBoot]')
|
|
16
16
|
* @param args - Arguments to log
|
|
17
17
|
*/
|
|
18
18
|
export declare const debugLog: (prefix: string, ...args: unknown[]) => void;
|
|
@@ -30,12 +30,12 @@ export declare const debugWarn: (prefix: string, ...args: unknown[]) => void;
|
|
|
30
30
|
export declare const debugError: (prefix: string, ...args: unknown[]) => void;
|
|
31
31
|
/**
|
|
32
32
|
* Create a namespaced debug logger
|
|
33
|
-
* @param namespace - Logger namespace (e.g., '
|
|
33
|
+
* @param namespace - Logger namespace (e.g., 'ColdBoot', 'DeviceAuth')
|
|
34
34
|
* @returns Object with log, warn, error methods
|
|
35
35
|
*
|
|
36
36
|
* @example
|
|
37
37
|
* ```ts
|
|
38
|
-
* const debug = createDebugLogger('
|
|
38
|
+
* const debug = createDebugLogger('ColdBoot');
|
|
39
39
|
* debug.log('Starting authentication');
|
|
40
40
|
* debug.warn('Token expires soon');
|
|
41
41
|
* debug.error('Authentication failed', error);
|
|
@@ -11,9 +11,8 @@ export interface QuickAccount {
|
|
|
11
11
|
avatarUrl?: string;
|
|
12
12
|
/**
|
|
13
13
|
* Device-local account slot index, 0..N-1 (Google-style multi-account).
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* remain valid; web flows always populate it after `refreshAllSessions`.
|
|
14
|
+
* Optional so that pre-multi-account QuickAccounts (sessionId-only) remain
|
|
15
|
+
* valid; the device session set populates it where available.
|
|
17
16
|
*/
|
|
18
17
|
authuser?: number;
|
|
19
18
|
/**
|
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Central IdP
|
|
2
|
+
* Central IdP apex constant.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* This module is intentionally pure: it performs no DOM access, reads no
|
|
12
|
-
* `window`/`location`, and has no side effects. It is the single source of
|
|
13
|
-
* truth for the central IdP origin so call sites never hardcode the literal.
|
|
14
|
-
*
|
|
15
|
-
* 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.
|
|
4
|
+
* The client SSO/FedCM resolvers (`resolveCentralAuthUrl`, `CENTRAL_AUTH_URL`)
|
|
5
|
+
* were removed in the device-first / legacy-final cutovers. The lone survivor is
|
|
6
|
+
* `CENTRAL_IDP_APEX`, kept because it has a LIVE consumer —
|
|
7
|
+
* `@oxyhq/core/server`'s CORS helper (`server/cors.ts`'s `createOxyCors`)
|
|
8
|
+
* auto-allows `*.oxy.so` from it. That CORS use is permanent, so this stays
|
|
9
|
+
* past the SSO/FedCM teardown.
|
|
20
10
|
*/
|
|
21
11
|
/**
|
|
22
12
|
* The registrable apex (eTLD+1) of the Oxy ecosystem's central Identity
|
|
23
|
-
* Provider
|
|
24
|
-
*
|
|
25
|
-
* regardless of which per-apex `auth.<rp>` host served a given request.
|
|
26
|
-
*
|
|
27
|
-
* Kept as a standalone constant so the IdP worker and the SDK derive the same
|
|
28
|
-
* literal from one source of truth (the worker imports it to brand assertions).
|
|
13
|
+
* Provider, reachable at `auth.${CENTRAL_IDP_APEX}`. Single source of truth so
|
|
14
|
+
* the CORS helper (and anything else that needs the central apex) never drifts.
|
|
29
15
|
*/
|
|
30
16
|
export declare const CENTRAL_IDP_APEX = "oxy.so";
|
|
31
|
-
/**
|
|
32
|
-
* The canonical central Identity Provider origin for the Oxy ecosystem.
|
|
33
|
-
* No trailing slash. Derived from {@link CENTRAL_IDP_APEX} so the apex and the
|
|
34
|
-
* full origin never drift apart. The IdP worker imports it to brand assertions.
|
|
35
|
-
*/
|
|
36
|
-
export declare const CENTRAL_AUTH_URL = "https://auth.oxy.so";
|
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
* authentication resolution.
|
|
4
4
|
*
|
|
5
5
|
* On a fresh page load / app launch the SDK may have several ways to recover an
|
|
6
|
-
* existing session (
|
|
7
|
-
*
|
|
8
|
-
* order
|
|
9
|
-
* skipped. This module encodes exactly that contract and
|
|
6
|
+
* existing session (a persisted refresh-token family, a shared-keychain
|
|
7
|
+
* identity, a cross-domain boot-fragment return, ...). They must be attempted
|
|
8
|
+
* in a deterministic order, and the FIRST one that yields a session wins —
|
|
9
|
+
* every later step is skipped. This module encodes exactly that contract and
|
|
10
|
+
* nothing else.
|
|
10
11
|
*
|
|
11
12
|
* Design constraints (all enforced):
|
|
12
13
|
* - PURE: no DOM, no `navigator`, no `window`, no React, no platform globals.
|
|
13
14
|
* - NO module-level mutable state. Every call to {@link runColdBoot} is fully
|
|
14
15
|
* self-contained, so it is safe under bundler re-evaluation (e.g. the Metro
|
|
15
|
-
* web bundle
|
|
16
|
-
*
|
|
17
|
-
* - Architecture-agnostic:
|
|
18
|
-
*
|
|
16
|
+
* web bundle — the reason any run-once guard for a step must live in the
|
|
17
|
+
* calling consumer, never in a core module-level singleton).
|
|
18
|
+
* - Architecture-agnostic: it knows nothing about HOW a step resolves a
|
|
19
|
+
* session; `runSessionColdBoot` (`boot/coldBootV2.ts`) is the current
|
|
20
|
+
* device-first consumer.
|
|
19
21
|
*
|
|
20
22
|
* A step is skipped (without running) when its `enabled` predicate returns
|
|
21
23
|
* false. Any thrown error — from either `enabled` or `run` — is reported via
|
|
@@ -92,15 +94,16 @@ export interface RunColdBootOptions<S> {
|
|
|
92
94
|
* fails to settle before the deadline, the runner abandons the await for that
|
|
93
95
|
* step (reporting it via `onStepDeadline`) and CONTINUES to the next step,
|
|
94
96
|
* each now racing against an already-expired deadline. This is deliberate:
|
|
95
|
-
* the runner keeps iterating so the TERMINAL step (e.g.
|
|
96
|
-
* whose `run()` performs its side effect
|
|
97
|
-
* `await`) still gets to fire. A step that
|
|
98
|
-
* the deadline simply doesn't settle and is
|
|
97
|
+
* the runner keeps iterating so the TERMINAL step (e.g. `coldBootV2`'s
|
|
98
|
+
* `bootstrap-hop`, whose `run()` performs its navigation side effect
|
|
99
|
+
* synchronously before its first `await`) still gets to fire. A step that
|
|
100
|
+
* has nothing to contribute after the deadline simply doesn't settle and is
|
|
101
|
+
* skipped in turn.
|
|
99
102
|
*
|
|
100
103
|
* Per-step timeouts inside `run()` remain the first line of defense and
|
|
101
104
|
* should keep every step well under this budget on a healthy load; this only
|
|
102
|
-
* trips when one of them regresses (
|
|
103
|
-
* omitted there is no overall deadline.
|
|
105
|
+
* trips when one of them regresses (a step hanging past its own timeout).
|
|
106
|
+
* When omitted there is no overall deadline.
|
|
104
107
|
*/
|
|
105
108
|
readonly overallDeadlineMs?: number;
|
|
106
109
|
/**
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registrable-apex (eTLD+1) host kernel.
|
|
3
3
|
*
|
|
4
|
-
* The client FAPI auto-detection helper
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* The client FAPI auto-detection helper was removed in the device-first cutover
|
|
5
|
+
* (which is why this file is now named for what it actually is, not the old
|
|
6
|
+
* `fapiAutoDetect`). What survives is the pure registrable-domain kernel, still
|
|
7
|
+
* used server-side by the api's device-first same-apex trust checks
|
|
8
|
+
* (`deviceAuth.ts`'s `POST /auth/device/web-session`, via `sameSite.ts`'s
|
|
9
|
+
* `isSameSiteTrustedRequest`) and the `@oxyhq/core/server` CORS/re-export layer.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* AND CloudWatch `/oxy/ecs` shows the `/sso*` + `/fedcm/*` routes quiet — the
|
|
14
|
-
* F-final sweep should remove this file then.
|
|
11
|
+
* `registrableApex` is NOT legacy — the device-first same-apex check is a live
|
|
12
|
+
* consumer, so this kernel stays regardless of the SSO/FedCM removal.
|
|
15
13
|
*/
|
|
16
14
|
/**
|
|
17
15
|
* Compute the bare registrable apex (eTLD+1) of a hostname using the Public
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"@oxyhq/contracts": "^0.
|
|
97
|
+
"@oxyhq/contracts": "^0.10.0",
|
|
98
98
|
"@oxyhq/protocol": "^0.1.1",
|
|
99
99
|
"bip39": "^3.1.0",
|
|
100
100
|
"buffer": "^6.0.3",
|
package/src/HttpService.ts
CHANGED
|
@@ -820,7 +820,7 @@ export class HttpService {
|
|
|
820
820
|
* `clearCacheByPrefix` sweeps and `clearCacheEntry` base-key matching.
|
|
821
821
|
* The `clearCacheEntry` callsites all pass fixed, dataless logical keys
|
|
822
822
|
* (`GET:/users/<id>`, `GET:/session/user/<sessionId>`,
|
|
823
|
-
* `GET:/
|
|
823
|
+
* `GET:/auth/grants`), so this readable suffix can never be
|
|
824
824
|
* ambiguous with a serialized request body.
|
|
825
825
|
*/
|
|
826
826
|
private static readonly CACHE_IDENTITY_DELIM = ' id=';
|
package/src/index.ts
CHANGED
|
@@ -34,9 +34,6 @@ export type { AuthRefreshReason, AuthRefreshHandler } from './HttpService';
|
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
35
|
// Authentication
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
|
-
// Legacy "Connected apps" management surface (list/revoke FedCM authorization
|
|
38
|
-
// grants). The FedCM sign-in machinery was removed in the device-first cutover.
|
|
39
|
-
export type { AuthorizedApp } from './mixins/OxyServices.authorizedApps';
|
|
40
37
|
export { ServiceCredentialMismatchError } from './mixins/OxyServices.auth';
|
|
41
38
|
export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
|
|
42
39
|
// "Sign in with Oxy" — handoff (Workstream C)
|
|
@@ -498,14 +495,18 @@ export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
|
|
|
498
495
|
// Registrable-domain + central-IdP-apex helpers.
|
|
499
496
|
//
|
|
500
497
|
// The client SSO-bounce / silent-iframe / FedCM machinery was removed in the
|
|
501
|
-
// device-first cutover
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
export
|
|
498
|
+
// device-first cutover (wave 2, ecosystem-wide bump complete). `registrableApex`
|
|
499
|
+
// (eTLD+1) and `CENTRAL_IDP_APEX` are still genuinely used: `registrableApex`
|
|
500
|
+
// via the `@oxyhq/core/server` re-export consumed by
|
|
501
|
+
// `packages/api/src/utils/sameSite.ts` for same-site origin checks, and
|
|
502
|
+
// `CENTRAL_IDP_APEX` by `server/cors.ts`'s `createOxyCors` (auto-allows
|
|
503
|
+
// `*.oxy.so`). `SSO_CALLBACK_PATH` has no remaining importer outside this
|
|
504
|
+
// module as of wave 2 — kept exported for now rather than removed here (an
|
|
505
|
+
// export deletion is a logic change, out of scope for a comment sweep); flag
|
|
506
|
+
// for a follow-up dead-export cleanup pass.
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
export { registrableApex } from './utils/registrableApex';
|
|
509
|
+
export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
|
|
509
510
|
export { SSO_CALLBACK_PATH } from './utils/ssoBounce';
|
|
510
511
|
|
|
511
512
|
export { runColdBoot } from './utils/coldBoot';
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
* SWITCHING INTO AN ACCOUNT: `switchToAccount(accountId)` mints a REAL session
|
|
29
29
|
* for the target account and plants it as the active session — there is no
|
|
30
30
|
* per-request "acting-as" header. Identity is carried by the session/token, not
|
|
31
|
-
* a delegation header, so a switch propagates through reload and
|
|
32
|
-
* exactly like a login
|
|
31
|
+
* a delegation header, so a switch propagates through reload and cross-domain
|
|
32
|
+
* exactly like a login, via the device-first session model (the server
|
|
33
|
+
* registers the switched session into the operator's device-set directly).
|
|
33
34
|
*/
|
|
34
35
|
import type { User } from '../models/interfaces';
|
|
35
36
|
import type { SessionLoginResponse } from '../models/session';
|
|
@@ -434,22 +435,22 @@ export interface AccountSuccessResult {
|
|
|
434
435
|
/**
|
|
435
436
|
* Result of {@link OxyServicesAccountsMixin.switchToAccount} — the freshly
|
|
436
437
|
* minted session for the target account, in the SAME shape the canonical login
|
|
437
|
-
* / `claimSessionByToken` responses use (`SessionLoginResponse`)
|
|
438
|
-
* device-local refresh-cookie slot index.
|
|
438
|
+
* / `claimSessionByToken` responses use (`SessionLoginResponse`).
|
|
439
439
|
*
|
|
440
440
|
* `accessToken` is the first access token for the new session (already planted
|
|
441
|
-
* as the active token by `switchToAccount`). The
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
441
|
+
* as the active token by `switchToAccount`). The switched session's survival
|
|
442
|
+
* across reload and cross-domain sync is device-first: the server registers
|
|
443
|
+
* it into the operator's `DeviceSession` set directly
|
|
444
|
+
* (`deviceSessionService.addAccount`, broadcast to the device room) — there is
|
|
445
|
+
* no client-side refresh-cookie slot to establish. `user` is the target
|
|
446
|
+
* account.
|
|
445
447
|
*/
|
|
446
448
|
export interface SwitchAccountResult extends SessionLoginResponse {
|
|
447
449
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* switch itself still succeeds).
|
|
450
|
+
* Legacy device-local refresh-cookie slot index. The current server switch
|
|
451
|
+
* response never sets this field (device-set registration replaced the
|
|
452
|
+
* cookie-slot model) — kept optional for backward type-compatibility with
|
|
453
|
+
* any caller still reading it, but always `undefined` in practice.
|
|
453
454
|
*/
|
|
454
455
|
authuser?: number;
|
|
455
456
|
}
|
|
@@ -515,24 +516,18 @@ export function OxyServicesAccountsMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
515
516
|
* target, directly or inherited — else 403; 404 if missing/archived; 403 if
|
|
516
517
|
* the target is a personal account), then mints a REAL session for the
|
|
517
518
|
* target account and returns it in the canonical login / `claimSessionByToken`
|
|
518
|
-
* shape (`{ sessionId, deviceId, expiresAt, accessToken, user
|
|
519
|
+
* shape (`{ sessionId, deviceId, expiresAt, accessToken, user }`).
|
|
519
520
|
*
|
|
520
521
|
* Unlike the removed `X-Acting-As` delegation header, the returned session
|
|
521
522
|
* IS the new identity: this plants `accessToken` as the active token —
|
|
522
523
|
* exactly like `claimSessionByToken` / `verifyChallenge` — so every
|
|
523
524
|
* subsequent request authenticates as the target account.
|
|
524
525
|
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
* would clobber slot 0 (destroying the operator's own session). `/auth/session`
|
|
531
|
-
* runs where those cookies ARE visible, so the server allocates a NEW slot that
|
|
532
|
-
* coexists with the operator's and returns its `authuser`. This step is
|
|
533
|
-
* web-only (native multi-account uses stored sessions, not cookies) and
|
|
534
|
-
* best-effort — a failure leaves the in-session switch intact; the switched
|
|
535
|
-
* account simply won't survive a reload until the cookie is next established.
|
|
526
|
+
* A single call is all that's needed: the server registers the switched
|
|
527
|
+
* session into the operator's `DeviceSession` set directly, inheriting the
|
|
528
|
+
* operator's central `deviceId` so the switch survives a reload and syncs
|
|
529
|
+
* cross-domain via the same device-first session model as a normal login —
|
|
530
|
+
* there is no separate client-side cookie/slot step to make it stick.
|
|
536
531
|
*
|
|
537
532
|
* After planting, the SDK's identity-scoped GET cache is fully cleared so
|
|
538
533
|
* every cached read re-fetches as the new account. (The consuming
|
|
@@ -542,7 +537,7 @@ export function OxyServicesAccountsMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
542
537
|
* same-user silent refreshes, so the sweep here is explicit.)
|
|
543
538
|
*
|
|
544
539
|
* @param accountId - The target account's Mongo `_id`.
|
|
545
|
-
* @returns The minted session
|
|
540
|
+
* @returns The minted session, already planted as the active session.
|
|
546
541
|
*/
|
|
547
542
|
async switchToAccount(accountId: string): Promise<SwitchAccountResult> {
|
|
548
543
|
try {
|
package/src/mixins/index.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import { OxyServicesBase } from '../OxyServices.base';
|
|
9
9
|
import { OxyServicesAuthMixin } from './OxyServices.auth';
|
|
10
|
-
import { OxyServicesAuthorizedAppsMixin } from './OxyServices.authorizedApps';
|
|
11
10
|
import { OxyServicesUserMixin } from './OxyServices.user';
|
|
12
11
|
import { OxyServicesIdentityMixin } from './OxyServices.identity';
|
|
13
12
|
import { OxyServicesPrivacyMixin } from './OxyServices.privacy';
|
|
@@ -42,7 +41,6 @@ import { OxyServicesDeviceBootMixin } from './OxyServices.deviceBoot';
|
|
|
42
41
|
*/
|
|
43
42
|
type AllMixinInstances =
|
|
44
43
|
& InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>>
|
|
45
|
-
& InstanceType<ReturnType<typeof OxyServicesAuthorizedAppsMixin<typeof OxyServicesBase>>>
|
|
46
44
|
& InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>>
|
|
47
45
|
& InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>>
|
|
48
46
|
& InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>>
|
|
@@ -96,11 +94,6 @@ const MIXIN_PIPELINE: MixinFunction[] = [
|
|
|
96
94
|
// Base authentication
|
|
97
95
|
OxyServicesAuthMixin,
|
|
98
96
|
|
|
99
|
-
// Legacy FedCM "Connected apps" management (list/revoke authorized RP grants).
|
|
100
|
-
// The FedCM sign-in surface was removed in the device-first cutover; only
|
|
101
|
-
// this management pair survives until the AppGrant migration.
|
|
102
|
-
OxyServicesAuthorizedAppsMixin,
|
|
103
|
-
|
|
104
97
|
// User management (requires auth)
|
|
105
98
|
OxyServicesUserMixin,
|
|
106
99
|
// Self-sovereign identity (DID, signed records, auth-method ↔ VM mapping)
|
package/src/models/session.ts
CHANGED
|
@@ -8,11 +8,10 @@ export interface ClientSession {
|
|
|
8
8
|
userId?: string;
|
|
9
9
|
isCurrent?: boolean;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Absent on native (RN uses the bearer-protected session id directly).
|
|
11
|
+
* The account's ordinal slot (0..N) within the device's account set
|
|
12
|
+
* (`SessionAccount.authuser` in `@oxyhq/contracts`), projected from the
|
|
13
|
+
* device-first `DeviceSessionState` — used purely for stable Google-style
|
|
14
|
+
* account-chooser ordering, not for any token-refresh mechanism.
|
|
16
15
|
*/
|
|
17
16
|
authuser?: number;
|
|
18
17
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export { verifySecret } from './verifySecret';
|
|
|
68
68
|
// SOURCE OF TRUTH shared with the IdP worker and the client FAPI auto-detect.
|
|
69
69
|
// Pure host handling (no browser deps), so it is safe on the server subpath and
|
|
70
70
|
// lets `@oxyhq/api` derive `auth.<apex>` without duplicating PSL logic.
|
|
71
|
-
export { registrableApex } from '../utils/
|
|
71
|
+
export { registrableApex } from '../utils/registrableApex';
|
|
72
72
|
|
|
73
73
|
// The single RP callback path the IdP redirects back to. A pure wire-contract
|
|
74
74
|
// constant (no browser deps at module top level), re-used server-side so the
|
|
@@ -9,7 +9,7 @@ import { getSocketIO } from './socketLoader';
|
|
|
9
9
|
import type { MinimalSocket, SocketIOFactory } from './socketLoader';
|
|
10
10
|
|
|
11
11
|
export interface TokenTransport {
|
|
12
|
-
/** Ensure this app holds a per-domain access token for state.activeAccountId (mint via
|
|
12
|
+
/** Ensure this app holds a per-domain access token for state.activeAccountId (mint via the persisted refresh family / shared keychain). Best-effort. */
|
|
13
13
|
ensureActiveToken(state: DeviceSessionState): Promise<void>;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -24,7 +24,7 @@ export const isDev = (): boolean => {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Log a debug message (only in development)
|
|
27
|
-
* @param prefix - Log prefix (e.g., '[
|
|
27
|
+
* @param prefix - Log prefix (e.g., '[ColdBoot]')
|
|
28
28
|
* @param args - Arguments to log
|
|
29
29
|
*/
|
|
30
30
|
export const debugLog = (prefix: string, ...args: unknown[]): void => {
|
|
@@ -57,12 +57,12 @@ export const debugError = (prefix: string, ...args: unknown[]): void => {
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Create a namespaced debug logger
|
|
60
|
-
* @param namespace - Logger namespace (e.g., '
|
|
60
|
+
* @param namespace - Logger namespace (e.g., 'ColdBoot', 'DeviceAuth')
|
|
61
61
|
* @returns Object with log, warn, error methods
|
|
62
62
|
*
|
|
63
63
|
* @example
|
|
64
64
|
* ```ts
|
|
65
|
-
* const debug = createDebugLogger('
|
|
65
|
+
* const debug = createDebugLogger('ColdBoot');
|
|
66
66
|
* debug.log('Starting authentication');
|
|
67
67
|
* debug.warn('Token expires soon');
|
|
68
68
|
* debug.error('Authentication failed', error);
|