@oxyhq/core 7.1.1 → 8.1.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/README.md +48 -24
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +6 -6
- package/dist/cjs/boot/coldBootV2.js +97 -2
- package/dist/cjs/boot/deviceBootReturn.js +15 -0
- package/dist/cjs/i18n/locales/en-US.json +44 -1
- package/dist/cjs/i18n/locales/es-ES.json +44 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +45 -2
- package/dist/cjs/i18n/locales/locales/es-ES.json +45 -2
- package/dist/cjs/index.js +19 -16
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +28 -0
- package/dist/cjs/server/index.js +1 -7
- package/dist/cjs/session/accountDialogController.js +1 -1
- package/dist/cjs/session/accountProjection.js +1 -1
- package/dist/cjs/session/authStateStore.js +6 -0
- package/dist/cjs/session/projectSessionState.js +1 -1
- package/dist/cjs/session/refresh.js +9 -0
- package/dist/cjs/session/sessionClientHost.js +1 -2
- package/dist/cjs/utils/accountUtils.js +1 -1
- package/dist/cjs/utils/oauthPkce.js +142 -0
- package/dist/cjs/utils/platform.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +6 -6
- package/dist/esm/boot/coldBootV2.js +97 -2
- package/dist/esm/boot/deviceBootReturn.js +15 -0
- package/dist/esm/i18n/locales/en-US.json +44 -1
- package/dist/esm/i18n/locales/es-ES.json +44 -1
- package/dist/esm/i18n/locales/locales/en-US.json +45 -2
- package/dist/esm/i18n/locales/locales/es-ES.json +45 -2
- package/dist/esm/index.js +11 -13
- package/dist/esm/mixins/OxyServices.deviceBoot.js +29 -1
- package/dist/esm/server/index.js +0 -5
- package/dist/esm/session/accountDialogController.js +1 -1
- package/dist/esm/session/accountProjection.js +1 -1
- package/dist/esm/session/authStateStore.js +6 -0
- package/dist/esm/session/projectSessionState.js +1 -1
- package/dist/esm/session/refresh.js +9 -0
- package/dist/esm/session/sessionClientHost.js +1 -2
- package/dist/esm/utils/accountUtils.js +1 -1
- package/dist/esm/utils/oauthPkce.js +135 -0
- package/dist/esm/utils/platform.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/mixins/OxyServices.accounts.d.ts +13 -3
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +4 -0
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +17 -1
- package/dist/types/mixins/OxyServices.devices.d.ts +3 -2
- package/dist/types/models/interfaces.d.ts +4 -4
- package/dist/types/server/index.d.ts +0 -1
- package/dist/types/session/accountDialogController.d.ts +1 -1
- package/dist/types/session/accountProjection.d.ts +1 -1
- package/dist/types/session/authStateStore.d.ts +20 -0
- package/dist/types/session/projectSessionState.d.ts +1 -1
- package/dist/types/session/refresh.d.ts +4 -8
- package/dist/types/session/sessionClientHost.d.ts +1 -2
- package/dist/types/utils/accountUtils.d.ts +1 -1
- package/dist/types/utils/oauthPkce.d.ts +74 -0
- package/dist/types/utils/platform.d.ts +1 -1
- package/package.json +3 -3
- package/src/HttpService.ts +6 -6
- package/src/boot/__tests__/coldBootV2.test.ts +215 -1
- package/src/boot/__tests__/deviceBootReturn.test.ts +32 -0
- package/src/boot/coldBootV2.ts +117 -2
- package/src/boot/deviceBootReturn.ts +15 -0
- package/src/i18n/locales/en-US.json +45 -2
- package/src/i18n/locales/es-ES.json +45 -2
- package/src/index.ts +23 -16
- package/src/mixins/OxyServices.accounts.ts +12 -0
- package/src/mixins/OxyServices.connectedApps.ts +4 -0
- package/src/mixins/OxyServices.deviceBoot.ts +38 -0
- package/src/mixins/OxyServices.devices.ts +6 -5
- package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +44 -1
- package/src/mixins/__tests__/accounts.test.ts +1 -1
- package/src/models/interfaces.ts +7 -5
- package/src/server/index.ts +0 -6
- package/src/session/__tests__/authStateStore.test.ts +27 -0
- package/src/session/__tests__/refresh.test.ts +14 -0
- package/src/session/accountDialogController.ts +1 -1
- package/src/session/accountProjection.ts +1 -1
- package/src/session/authStateStore.ts +26 -0
- package/src/session/projectSessionState.ts +1 -1
- package/src/session/refresh.ts +13 -8
- package/src/session/sessionClientHost.ts +1 -2
- package/src/utils/__tests__/coldBoot.test.ts +55 -65
- package/src/utils/__tests__/oauthPkce.test.ts +154 -0
- package/src/utils/accountUtils.ts +1 -1
- package/src/utils/oauthPkce.ts +189 -0
- package/src/utils/platform.ts +1 -1
- package/dist/cjs/utils/ssoBounce.js +0 -24
- package/dist/esm/utils/ssoBounce.js +0 -21
- package/dist/types/utils/ssoBounce.d.ts +0 -21
- package/src/utils/ssoBounce.ts +0 -22
|
@@ -85,7 +85,7 @@ export declare class HttpService {
|
|
|
85
85
|
private cacheSizeWarningSilentUntil;
|
|
86
86
|
/**
|
|
87
87
|
* Fan-out listeners notified on EVERY access-token change on this instance:
|
|
88
|
-
* explicit `setTokens`, `clearTokens`,
|
|
88
|
+
* explicit `setTokens`, `clearTokens`, a refresh-handler rotation, and the
|
|
89
89
|
* internal 401-driven clear. This is a Set so multiple independent observers
|
|
90
90
|
* can mirror token state without clobbering each other.
|
|
91
91
|
*
|
package/dist/types/index.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export { RecoveryPhraseService } from './crypto/recoveryPhrase';
|
|
|
53
53
|
export type { RecoveryPhraseResult } from './crypto/recoveryPhrase';
|
|
54
54
|
export { DeviceManager } from './utils/deviceManager';
|
|
55
55
|
export type { DeviceFingerprint, StoredDeviceInfo } from './utils/deviceManager';
|
|
56
|
-
export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress,
|
|
56
|
+
export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceLinkedSession, DeviceLinkedSessionsResponse, DeviceLinkedSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
|
|
57
57
|
export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
|
|
58
58
|
export { TopicType, TopicSource } from './models/Topic';
|
|
59
59
|
export type { TopicData, TopicTranslation } from './models/Topic';
|
|
@@ -81,9 +81,10 @@ export { buildAccountsArray, createQuickAccount, getAccountDisplayName, getAccou
|
|
|
81
81
|
export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
|
|
82
82
|
export { registrableApex } from './utils/registrableApex';
|
|
83
83
|
export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
|
|
84
|
-
export { SSO_CALLBACK_PATH } from './utils/ssoBounce';
|
|
85
84
|
export { runColdBoot } from './utils/coldBoot';
|
|
86
85
|
export type { ColdBootStep, ColdBootStepResult, ColdBootSession, ColdBootSkip, ColdBootOutcome, RunColdBootOptions, } from './utils/coldBoot';
|
|
86
|
+
export { buildOAuthAuthorizeUrl, computeCodeChallenge, generateOAuthState, generatePkcePair, DEFAULT_OAUTH_SCOPE, OXY_AUTHORIZE_URL, } from './utils/oauthPkce';
|
|
87
|
+
export type { PkcePair, BuildOAuthAuthorizeUrlParams } from './utils/oauthPkce';
|
|
87
88
|
export { SessionClient } from './session/SessionClient';
|
|
88
89
|
export type { TokenTransport, SessionClientHost, SessionClientOptions } from './session/SessionClient';
|
|
89
90
|
export type { SocketIOFactory, MinimalSocket } from './session/socketLoader';
|
|
@@ -250,6 +250,10 @@ export interface Application {
|
|
|
250
250
|
name: string;
|
|
251
251
|
description?: string;
|
|
252
252
|
websiteUrl?: string;
|
|
253
|
+
/** Public privacy-policy URL, rendered as a legal link on the OAuth consent screen. */
|
|
254
|
+
privacyPolicyUrl?: string;
|
|
255
|
+
/** Public terms-of-service URL, rendered as a legal link on the OAuth consent screen. */
|
|
256
|
+
termsUrl?: string;
|
|
253
257
|
icon?: string;
|
|
254
258
|
type: ApplicationType;
|
|
255
259
|
status: ApplicationStatus;
|
|
@@ -306,6 +310,10 @@ export interface CreateApplicationInput {
|
|
|
306
310
|
name: string;
|
|
307
311
|
description?: string;
|
|
308
312
|
websiteUrl?: string;
|
|
313
|
+
/** Public privacy-policy URL (absolute `https://`). Shown on the OAuth consent screen. */
|
|
314
|
+
privacyPolicyUrl?: string;
|
|
315
|
+
/** Public terms-of-service URL (absolute `https://`). Shown on the OAuth consent screen. */
|
|
316
|
+
termsUrl?: string;
|
|
309
317
|
icon?: string;
|
|
310
318
|
redirectUris?: string[];
|
|
311
319
|
scopes?: string[];
|
|
@@ -320,6 +328,10 @@ export interface UpdateApplicationInput {
|
|
|
320
328
|
name?: string;
|
|
321
329
|
description?: string;
|
|
322
330
|
websiteUrl?: string;
|
|
331
|
+
/** Public privacy-policy URL (absolute `https://`, or `''` to clear). Shown on the OAuth consent screen. */
|
|
332
|
+
privacyPolicyUrl?: string;
|
|
333
|
+
/** Public terms-of-service URL (absolute `https://`, or `''` to clear). Shown on the OAuth consent screen. */
|
|
334
|
+
termsUrl?: string;
|
|
323
335
|
icon?: string;
|
|
324
336
|
redirectUris?: string[];
|
|
325
337
|
scopes?: string[];
|
|
@@ -689,9 +701,7 @@ export declare function OxyServicesAccountsMixin<T extends typeof OxyServicesBas
|
|
|
689
701
|
handleError(error: unknown): Error;
|
|
690
702
|
healthCheck(): Promise<{
|
|
691
703
|
status: string;
|
|
692
|
-
users
|
|
693
|
-
/** Aggregate totals for an application over the requested period. */
|
|
694
|
-
? /** Aggregate totals for an application over the requested period. */: number;
|
|
704
|
+
users?: number;
|
|
695
705
|
timestamp?: string;
|
|
696
706
|
[key: string]: any;
|
|
697
707
|
}>;
|
|
@@ -37,6 +37,10 @@ export interface PublicApplication {
|
|
|
37
37
|
icon?: string;
|
|
38
38
|
/** Optional public website/homepage URL for the application. */
|
|
39
39
|
websiteUrl?: string;
|
|
40
|
+
/** Optional public privacy-policy URL, rendered as a legal link on the consent screen. */
|
|
41
|
+
privacyPolicyUrl?: string;
|
|
42
|
+
/** Optional public terms-of-service URL, rendered as a legal link on the consent screen. */
|
|
43
|
+
termsUrl?: string;
|
|
40
44
|
/** Application classification (set by Oxy platform staff). */
|
|
41
45
|
type: ApplicationType;
|
|
42
46
|
/** Whether the application is an officially endorsed Oxy application. */
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* and `setTokens`, so the same network primitive can be reused from either
|
|
16
16
|
* without double-planting.
|
|
17
17
|
*/
|
|
18
|
-
import { type AuthTokenBundle, type TokenRefreshResponse, type WebSessionResult } from '@oxyhq/contracts';
|
|
18
|
+
import { type AuthTokenBundle, type DeviceTokenMintResponse, type TokenRefreshResponse, type WebSessionResult } from '@oxyhq/contracts';
|
|
19
19
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
20
20
|
export declare function OxyServicesDeviceBootMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
21
21
|
new (...args: any[]): {
|
|
@@ -51,6 +51,22 @@ export declare function OxyServicesDeviceBootMixin<T extends typeof OxyServicesB
|
|
|
51
51
|
* @throws if the response does not match {@link deviceTokenIssueResponseSchema}.
|
|
52
52
|
*/
|
|
53
53
|
issueNativeDeviceToken(): Promise<string>;
|
|
54
|
+
/**
|
|
55
|
+
* Zero-cookie mint (phase 2c). Present the first-party `deviceId` +
|
|
56
|
+
* `deviceSecret` to `POST /session/device/token` — NO bearer, NO cookies:
|
|
57
|
+
* possession of the secret IS the device-ownership proof. Returns a fresh
|
|
58
|
+
* short access token for the device's active account plus `nextDeviceSecret`
|
|
59
|
+
* (rotation-in-use) and the projected device-session `state`.
|
|
60
|
+
*
|
|
61
|
+
* `skipAuth` (like {@link refreshWithToken}): this call carries no bearer, so
|
|
62
|
+
* a 401 must surface DIRECTLY — never trigger `HttpService`'s 401→refresh→
|
|
63
|
+
* retry dance (which would pointlessly rotate the refresh family). The cold
|
|
64
|
+
* boot reads the 401 body (`invalid_device_secret` vs `no_active_session`) to
|
|
65
|
+
* decide whether to drop the secret and fall back or resolve signed-out.
|
|
66
|
+
*
|
|
67
|
+
* @throws if the response does not match {@link deviceTokenMintResponseSchema}.
|
|
68
|
+
*/
|
|
69
|
+
mintFromDeviceSecret(deviceId: string, deviceSecret: string): Promise<DeviceTokenMintResponse>;
|
|
54
70
|
/**
|
|
55
71
|
* Build the top-level `GET /auth/device/bootstrap` URL for the cross-apex
|
|
56
72
|
* hop. The server validates `return_to` against the trusted-origin lane and
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Device Methods Mixin
|
|
3
3
|
*/
|
|
4
4
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
5
|
+
import type { DeviceLinkedSession, DeviceLinkedSessionLogoutResponse } from '../models/interfaces';
|
|
5
6
|
export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
6
7
|
new (...args: any[]): {
|
|
7
8
|
/**
|
|
@@ -26,7 +27,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
|
|
|
26
27
|
* @param sessionId - The session ID
|
|
27
28
|
* @returns Array of device sessions
|
|
28
29
|
*/
|
|
29
|
-
getDeviceSessions(sessionId: string): Promise<
|
|
30
|
+
getDeviceSessions(sessionId: string): Promise<DeviceLinkedSession[]>;
|
|
30
31
|
/**
|
|
31
32
|
* Logout all device sessions
|
|
32
33
|
* @param sessionId - The session ID
|
|
@@ -34,7 +35,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
|
|
|
34
35
|
* @param excludeCurrent - Whether to exclude the current session
|
|
35
36
|
* @returns Logout result
|
|
36
37
|
*/
|
|
37
|
-
logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent?: boolean): Promise<
|
|
38
|
+
logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent?: boolean): Promise<DeviceLinkedSessionLogoutResponse>;
|
|
38
39
|
/**
|
|
39
40
|
* Update device name
|
|
40
41
|
* @param sessionId - The session ID
|
|
@@ -545,7 +545,7 @@ export interface AssetUploadProgress {
|
|
|
545
545
|
status: 'uploading' | 'processing' | 'complete' | 'error';
|
|
546
546
|
error?: string;
|
|
547
547
|
}
|
|
548
|
-
export interface
|
|
548
|
+
export interface DeviceLinkedSession {
|
|
549
549
|
sessionId: string;
|
|
550
550
|
deviceId: string;
|
|
551
551
|
deviceName: string;
|
|
@@ -556,11 +556,11 @@ export interface DeviceSession {
|
|
|
556
556
|
user?: User;
|
|
557
557
|
createdAt?: string;
|
|
558
558
|
}
|
|
559
|
-
export interface
|
|
559
|
+
export interface DeviceLinkedSessionsResponse {
|
|
560
560
|
deviceId: string;
|
|
561
|
-
sessions:
|
|
561
|
+
sessions: DeviceLinkedSession[];
|
|
562
562
|
}
|
|
563
|
-
export interface
|
|
563
|
+
export interface DeviceLinkedSessionLogoutResponse {
|
|
564
564
|
message: string;
|
|
565
565
|
deviceId: string;
|
|
566
566
|
sessionsTerminated: number;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A framework-agnostic state machine + subscribe/getSnapshot store (the same
|
|
5
5
|
* pattern {@link SessionClient} uses — no React, no RN) that both
|
|
6
|
-
*
|
|
6
|
+
* every `OxyProvider` platform variant (Expo/RN and RN-Web)
|
|
7
7
|
* bind to via `useSyncExternalStore`, so the account chooser is ONE
|
|
8
8
|
* implementation across the ecosystem instead of the five drifting copies it
|
|
9
9
|
* replaces.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* merging the device's server-authoritative session set (`DeviceSessionState`
|
|
6
6
|
* from {@link SessionClient}) with the caller's account graph (`AccountNode[]`
|
|
7
7
|
* from `oxyServices.listAccounts()`), deduped by `accountId`. This lives in
|
|
8
|
-
* `@oxyhq/core` so `@oxyhq/services`
|
|
8
|
+
* `@oxyhq/core` so every `@oxyhq/services` platform variant — and
|
|
9
9
|
* `auth.oxy.so` — all render the SAME list from the SAME logic and cannot
|
|
10
10
|
* diverge.
|
|
11
11
|
*
|
|
@@ -38,6 +38,26 @@ export interface PersistedAuthState {
|
|
|
38
38
|
refreshToken: string;
|
|
39
39
|
userId: string;
|
|
40
40
|
deviceToken?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The stable device identifier this session is bound to (phase 2c —
|
|
43
|
+
* zero-cookie transport). Persisted alongside {@link deviceSecret} because the
|
|
44
|
+
* `POST /session/device/token` mint presents BOTH — the secret is the proof,
|
|
45
|
+
* the deviceId selects the device doc. Sourced from the lanes that carry it
|
|
46
|
+
* (password login / 2FA / QR claim / challenge verify); the cookie-bootstrap
|
|
47
|
+
* lanes (`AuthTokenBundle`) omit it and preserve any prior value. Additive: a
|
|
48
|
+
* blob without it simply never takes the mint lane and falls back to the
|
|
49
|
+
* refresh family.
|
|
50
|
+
*/
|
|
51
|
+
deviceId?: string;
|
|
52
|
+
/**
|
|
53
|
+
* The rotating device secret (phase 2c — zero-cookie transport). Possession of
|
|
54
|
+
* it mints a short access token for the device's active account via
|
|
55
|
+
* `POST /session/device/token`, replacing the cookie lane. Rotated in-use: the
|
|
56
|
+
* mint returns `nextDeviceSecret`, which the cold boot persists BEFORE planting
|
|
57
|
+
* the minted access token (multi-tab anti-loss). Additive and optional — same
|
|
58
|
+
* XSS risk profile as the already-persisted `refreshToken`.
|
|
59
|
+
*/
|
|
60
|
+
deviceSecret?: string;
|
|
41
61
|
/** Optional warm-boot access token (short-lived; see interface docs). */
|
|
42
62
|
accessToken?: string;
|
|
43
63
|
/** Optional warm-boot access-token expiry, ISO-8601. */
|
|
@@ -4,7 +4,7 @@ import type { User } from '../models/interfaces';
|
|
|
4
4
|
/**
|
|
5
5
|
* Pure projection helpers: `DeviceSessionState` (the device-scoped
|
|
6
6
|
* multi-account session-sync state produced by `SessionClient`) -> the
|
|
7
|
-
* shapes
|
|
7
|
+
* shapes `@oxyhq/services` consumers render today
|
|
8
8
|
* (`ClientSession[]`, an active session id, an active `User`).
|
|
9
9
|
*
|
|
10
10
|
* No I/O. The caller fetches profiles via
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified token refresh — THE single refresh implementation for web + native.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `@oxyhq/services`'s `inSessionTokenRefresh.ts` (native shared-key). This
|
|
7
|
-
* module replaces both with ONE persisted-refresh-token rotation shared by
|
|
8
|
-
* every consumer:
|
|
4
|
+
* ONE persisted-refresh-token rotation shared by every consumer (it replaced
|
|
5
|
+
* the pre-device-first per-platform duplicates):
|
|
9
6
|
*
|
|
10
7
|
* - `refreshPersistedSession` — arm 1 rotates the stored refresh-token family
|
|
11
8
|
* (`POST /auth/refresh-token`), planting + persisting the rotated pair; arm 2
|
|
@@ -16,9 +13,8 @@
|
|
|
16
13
|
* - `createAuthRefreshHandler` / `installAuthRefreshHandler` wire arm 1+2 into
|
|
17
14
|
* `HttpService.setAuthRefreshHandler`, keeping that layer's single-flight
|
|
18
15
|
* dedup + cooldown (this module does NOT reimplement them).
|
|
19
|
-
* - `startTokenRefreshScheduler` — a proactive scheduler
|
|
20
|
-
*
|
|
21
|
-
* decoupled from any React / auth-sdk type: refreshes ~60s before `exp`,
|
|
16
|
+
* - `startTokenRefreshScheduler` — a proactive scheduler decoupled from any
|
|
17
|
+
* React type: refreshes ~60s before `exp`,
|
|
22
18
|
* re-arms on token change + web tab-focus, `.unref?.()`s its timer in Node.
|
|
23
19
|
*
|
|
24
20
|
* Framework-free; no module-level mutable state.
|
|
@@ -6,8 +6,7 @@ import type { SessionClientHost } from './SessionClient';
|
|
|
6
6
|
* `SessionClient` is host-agnostic: it only needs a REST + token surface.
|
|
7
7
|
* `OxyServices` already exposes all of that except `getCurrentAccountId`,
|
|
8
8
|
* which has no direct equivalent — the adapter holds a mutable ref set by
|
|
9
|
-
* the caller (`OxyContext` in `@oxyhq/services
|
|
10
|
-
* `@oxyhq/auth`) via `setCurrentAccountId`.
|
|
9
|
+
* the caller (`OxyContext` in `@oxyhq/services`) via `setCurrentAccountId`.
|
|
11
10
|
*
|
|
12
11
|
* Shared here (rather than duplicated per consumer) because it is entirely
|
|
13
12
|
* platform-agnostic: every method it calls exists identically on
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared account types and pure helper functions.
|
|
3
|
-
* Used by
|
|
3
|
+
* Used by the @oxyhq/services account stores (Expo/RN and RN-Web).
|
|
4
4
|
*/
|
|
5
5
|
export interface QuickAccount {
|
|
6
6
|
sessionId: string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 Authorization Code + PKCE helpers for "Sign in with Oxy" third-party
|
|
3
|
+
* sign-in.
|
|
4
|
+
*
|
|
5
|
+
* Third-party Relying Parties (SPAs, static sites, and native apps that are NOT
|
|
6
|
+
* Oxy first-party) authenticate through the standard OAuth flow against
|
|
7
|
+
* `auth.oxy.so/authorize` — never FedCM, SSO bounces, or Oxy session cookies.
|
|
8
|
+
* Public clients (no secret) prove possession of the authorization code with
|
|
9
|
+
* PKCE (RFC 7636, S256): the RP generates a random `code_verifier`, sends its
|
|
10
|
+
* `code_challenge = BASE64URL(SHA-256(code_verifier))` on the authorize
|
|
11
|
+
* redirect, and later replays the raw verifier on the token exchange.
|
|
12
|
+
*
|
|
13
|
+
* All cross-platform crypto (random bytes, SHA-256) is delegated to the shared
|
|
14
|
+
* `@oxyhq/protocol` platform loaders — the exact primitives the rest of core's
|
|
15
|
+
* crypto already uses — so these helpers run identically on web, Node, and
|
|
16
|
+
* React Native. No `require()`, so the ESM build stays bundler-clean.
|
|
17
|
+
*/
|
|
18
|
+
/** The central Oxy IdP authorization endpoint used by default. */
|
|
19
|
+
export declare const OXY_AUTHORIZE_URL = "https://auth.oxy.so/authorize";
|
|
20
|
+
/** Default OAuth scope requested for a "Sign in with Oxy" third-party flow. */
|
|
21
|
+
export declare const DEFAULT_OAUTH_SCOPE = "openid profile";
|
|
22
|
+
/** A generated PKCE verifier/challenge pair. */
|
|
23
|
+
export interface PkcePair {
|
|
24
|
+
/** The high-entropy secret replayed on the token exchange (kept client-side). */
|
|
25
|
+
codeVerifier: string;
|
|
26
|
+
/** `BASE64URL(SHA-256(codeVerifier))` — sent on the authorize redirect. */
|
|
27
|
+
codeChallenge: string;
|
|
28
|
+
/** The PKCE transformation method. Always `S256`. */
|
|
29
|
+
method: 'S256';
|
|
30
|
+
}
|
|
31
|
+
/** Parameters for {@link buildOAuthAuthorizeUrl}. */
|
|
32
|
+
export interface BuildOAuthAuthorizeUrlParams {
|
|
33
|
+
/** Authorize endpoint; defaults to {@link OXY_AUTHORIZE_URL}. */
|
|
34
|
+
authorizeBaseUrl?: string;
|
|
35
|
+
/** The registered `ApplicationCredential` public key (`oxy_dk_…`). */
|
|
36
|
+
clientId: string;
|
|
37
|
+
/** Exact registered redirect URI to return the authorization code to. */
|
|
38
|
+
redirectUri: string;
|
|
39
|
+
/** Requested scope; defaults to {@link DEFAULT_OAUTH_SCOPE}. */
|
|
40
|
+
scope?: string;
|
|
41
|
+
/** Opaque CSRF token from {@link generateOAuthState}. */
|
|
42
|
+
state: string;
|
|
43
|
+
/** The PKCE `codeChallenge` from {@link generatePkcePair}. */
|
|
44
|
+
codeChallenge: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compute the PKCE S256 `code_challenge` for a given verifier:
|
|
48
|
+
* `BASE64URL(SHA-256(ASCII(codeVerifier)))` (RFC 7636 §4.2). The verifier is
|
|
49
|
+
* base64url (ASCII), so its UTF-8 and ASCII byte encodings are identical.
|
|
50
|
+
*
|
|
51
|
+
* Reuses `@oxyhq/protocol`'s cross-platform {@link sha256} (which returns
|
|
52
|
+
* lowercase hex); the digest bytes are recovered and re-encoded as base64url.
|
|
53
|
+
*/
|
|
54
|
+
export declare function computeCodeChallenge(codeVerifier: string): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Generate a fresh PKCE verifier/challenge pair for an OAuth authorization-code
|
|
57
|
+
* flow. The verifier is 64 random bytes as base64url (86 chars, within RFC 7636
|
|
58
|
+
* §4.1's 43–128 range and drawn only from the unreserved set); the challenge is
|
|
59
|
+
* its S256 transform.
|
|
60
|
+
*/
|
|
61
|
+
export declare function generatePkcePair(): Promise<PkcePair>;
|
|
62
|
+
/**
|
|
63
|
+
* Generate an opaque, single-use OAuth `state` token (32 random bytes as
|
|
64
|
+
* base64url) for CSRF protection across the authorize redirect.
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateOAuthState(): Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Build the `auth.oxy.so/authorize` redirect URL for an OAuth authorization-code
|
|
69
|
+
* + PKCE (S256) flow. Built via the WHATWG `URL` API so a custom
|
|
70
|
+
* `authorizeBaseUrl` that already carries a query string keeps its existing
|
|
71
|
+
* params (the OAuth params are merged in, not clobbered by a naive `?` concat).
|
|
72
|
+
* All values are percent-encoded by `URL.searchParams`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildOAuthAuthorizeUrl(params: BuildOAuthAuthorizeUrlParams): string;
|
|
@@ -39,7 +39,7 @@ export declare function setPlatformOS(os: PlatformOS): void;
|
|
|
39
39
|
*
|
|
40
40
|
* Native defines a global `window` but no `document`, so the DOM probe — not a
|
|
41
41
|
* bare `window` check — is the reliable discriminator. This is the single
|
|
42
|
-
* source of truth consumed by `@oxyhq/services`
|
|
42
|
+
* source of truth consumed by `@oxyhq/services` (which dropped
|
|
43
43
|
* their local copies), so every consumer shares the exact same predicate.
|
|
44
44
|
*
|
|
45
45
|
* NOTE: this is a live runtime probe (not the cached `getPlatformOS()` verdict)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.1.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,8 +94,8 @@
|
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"@oxyhq/contracts": "^0.
|
|
98
|
-
"@oxyhq/protocol": "^0.1.
|
|
97
|
+
"@oxyhq/contracts": "^0.12.0",
|
|
98
|
+
"@oxyhq/protocol": "^0.1.2",
|
|
99
99
|
"bip39": "^3.1.0",
|
|
100
100
|
"buffer": "^6.0.3",
|
|
101
101
|
"elliptic": "^6.6.1",
|
package/src/HttpService.ts
CHANGED
|
@@ -128,7 +128,7 @@ const CSRF_FETCH_RETRY_DELAY_MS = 500;
|
|
|
128
128
|
/**
|
|
129
129
|
* Cooldown (ms) applied after a failed access-token refresh before another
|
|
130
130
|
* refresh is attempted. Prevents a refresh storm (and server hammering) when
|
|
131
|
-
* the
|
|
131
|
+
* the auth refresh handler is failing — every in-flight request that
|
|
132
132
|
* hits a 401 would otherwise trigger its own refresh.
|
|
133
133
|
*/
|
|
134
134
|
const TOKEN_REFRESH_COOLDOWN_MS = 15000;
|
|
@@ -244,7 +244,7 @@ export class HttpService {
|
|
|
244
244
|
|
|
245
245
|
/**
|
|
246
246
|
* Fan-out listeners notified on EVERY access-token change on this instance:
|
|
247
|
-
* explicit `setTokens`, `clearTokens`,
|
|
247
|
+
* explicit `setTokens`, `clearTokens`, a refresh-handler rotation, and the
|
|
248
248
|
* internal 401-driven clear. This is a Set so multiple independent observers
|
|
249
249
|
* can mirror token state without clobbering each other.
|
|
250
250
|
*
|
|
@@ -527,9 +527,9 @@ export class HttpService {
|
|
|
527
527
|
|
|
528
528
|
// Handle response
|
|
529
529
|
if (!response.ok) {
|
|
530
|
-
// On 401, delegate refresh
|
|
531
|
-
// giving up. HttpService deliberately does not know any
|
|
532
|
-
// routes; the
|
|
530
|
+
// On 401, delegate to the installed auth refresh handler and retry
|
|
531
|
+
// once before giving up. HttpService deliberately does not know any
|
|
532
|
+
// session routes; the refresh handler owns session rotation.
|
|
533
533
|
if (response.status === 401 && !config._isAuthRetry && !config.skipAuth) {
|
|
534
534
|
const refreshed = await this.refreshAccessToken('response-401');
|
|
535
535
|
if (refreshed) {
|
|
@@ -1045,7 +1045,7 @@ export class HttpService {
|
|
|
1045
1045
|
this.tokenStore.setTokens(newToken);
|
|
1046
1046
|
this.notifyTokenChange();
|
|
1047
1047
|
}
|
|
1048
|
-
this.logger.debug('Token refreshed via
|
|
1048
|
+
this.logger.debug('Token refreshed via the auth refresh handler');
|
|
1049
1049
|
return newToken;
|
|
1050
1050
|
})
|
|
1051
1051
|
.catch((error) => {
|