@oxyhq/core 7.1.0 → 8.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/README.md +48 -24
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +6 -6
- 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/server/index.js +1 -7
- package/dist/cjs/session/accountDialogController.js +88 -3
- package/dist/cjs/session/accountProjection.js +1 -1
- package/dist/cjs/session/projectSessionState.js +1 -1
- 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/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/server/index.js +0 -5
- package/dist/esm/session/accountDialogController.js +88 -3
- package/dist/esm/session/accountProjection.js +1 -1
- package/dist/esm/session/projectSessionState.js +1 -1
- 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.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 +28 -1
- package/dist/types/session/accountProjection.d.ts +1 -1
- 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/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.devices.ts +6 -5
- 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__/accountDialogController.test.ts +123 -0
- package/src/session/accountDialogController.ts +90 -3
- package/src/session/accountProjection.ts +1 -1
- package/src/session/projectSessionState.ts +1 -1
- package/src/session/refresh.ts +4 -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. */
|
|
@@ -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.
|
|
@@ -131,6 +131,9 @@ export declare class AccountDialogController {
|
|
|
131
131
|
private signInToken;
|
|
132
132
|
private pollTimer;
|
|
133
133
|
private unsubscribeSession;
|
|
134
|
+
private unsubscribeTokens;
|
|
135
|
+
/** Last-observed SDK auth readiness (a planted bearer). Drives the fetch edge. */
|
|
136
|
+
private authed;
|
|
134
137
|
private started;
|
|
135
138
|
private refreshSeq;
|
|
136
139
|
private snapshot;
|
|
@@ -150,6 +153,30 @@ export declare class AccountDialogController {
|
|
|
150
153
|
* active sign-in flow (timers). Idempotent.
|
|
151
154
|
*/
|
|
152
155
|
destroy(): void;
|
|
156
|
+
/**
|
|
157
|
+
* Whether a PRIVATE endpoint may be called right now. Mirrors the
|
|
158
|
+
* `hasAccessToken` term of `OxyContext.canUsePrivateApi`
|
|
159
|
+
* (`authResolved && isAuthenticated && tokenReady && hasAccessToken`, where
|
|
160
|
+
* `hasAccessToken = Boolean(oxyServices.getAccessToken())`): a planted bearer
|
|
161
|
+
* is the only term that decides whether a request carries auth — the other
|
|
162
|
+
* three are provider render-lifecycle gates with no headless equivalent.
|
|
163
|
+
*
|
|
164
|
+
* `listAccounts()` (`GET /accounts`) and `getUsersByIds()`
|
|
165
|
+
* (`POST /users/by-ids`) are private; calling either before cold-boot restore
|
|
166
|
+
* plants the token 401s → `HttpService` clears the bearer + emits
|
|
167
|
+
* `onTokensChanged(null)` → the app signs out. Every graph/profile fetch gates
|
|
168
|
+
* on this.
|
|
169
|
+
*/
|
|
170
|
+
private isAuthenticated;
|
|
171
|
+
/**
|
|
172
|
+
* Reconcile the account graph against the current auth-readiness edge. On the
|
|
173
|
+
* signed-out → signed-in edge fetch the graph ONCE; on signed-in → signed-out
|
|
174
|
+
* drop it and re-project device-only. A no-op when readiness is unchanged, so
|
|
175
|
+
* a burst of token events / device pushes cannot restart the fetch — and a
|
|
176
|
+
* failed `listAccounts()` never flips the edge, so it cannot re-trigger itself
|
|
177
|
+
* (no retry storm).
|
|
178
|
+
*/
|
|
179
|
+
private reconcileAuth;
|
|
153
180
|
/** Set the dialog view directly. */
|
|
154
181
|
setView(view: AccountDialogView): void;
|
|
155
182
|
/** Return to the account list and cancel any in-flight sign-in flow. */
|
|
@@ -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
|
*
|
|
@@ -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.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,8 +94,8 @@
|
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"@oxyhq/contracts": "^0.
|
|
98
|
-
"@oxyhq/protocol": "^0.1.
|
|
97
|
+
"@oxyhq/contracts": "^0.11.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) => {
|
|
@@ -384,7 +384,11 @@
|
|
|
384
384
|
"subtitle": "Add another account to switch between them quickly"
|
|
385
385
|
},
|
|
386
386
|
"label": "Account switcher",
|
|
387
|
-
"searchPlaceholder": "Search accounts"
|
|
387
|
+
"searchPlaceholder": "Search accounts",
|
|
388
|
+
"scanTitle": "Scan to sign in",
|
|
389
|
+
"scanSubtitle": "Open the Oxy app on your phone and scan this code",
|
|
390
|
+
"scanWithOxy": "Scan with the Oxy app to continue",
|
|
391
|
+
"scanQr": "Scan QR code"
|
|
388
392
|
},
|
|
389
393
|
"reputation": {
|
|
390
394
|
"faq": {
|
|
@@ -1905,5 +1909,44 @@
|
|
|
1905
1909
|
"earlyAdopter": "Early Adopter",
|
|
1906
1910
|
"earlyAdopterDesc": "Been part of the community from the start"
|
|
1907
1911
|
}
|
|
1912
|
+
},
|
|
1913
|
+
"consent": {
|
|
1914
|
+
"title": "Continue to {{app}}",
|
|
1915
|
+
"subtitle": "Use your Oxy account to sign in to {{app}}. Review what this connection means before you continue.",
|
|
1916
|
+
"provenance": {
|
|
1917
|
+
"title": "Who is requesting access",
|
|
1918
|
+
"official": "Official Oxy application",
|
|
1919
|
+
"developer": "Published by {{developer}}",
|
|
1920
|
+
"thirdParty": "Third-party application"
|
|
1921
|
+
},
|
|
1922
|
+
"permissions": {
|
|
1923
|
+
"title": "Permissions requested",
|
|
1924
|
+
"basic": "Sign you in and read your basic profile"
|
|
1925
|
+
},
|
|
1926
|
+
"scopes": {
|
|
1927
|
+
"openid": "Confirm your identity",
|
|
1928
|
+
"profile": "Read your basic profile",
|
|
1929
|
+
"email": "Read your email address",
|
|
1930
|
+
"offlineAccess": "Keep you signed in when you're away",
|
|
1931
|
+
"userRead": "Read your basic profile",
|
|
1932
|
+
"filesRead": "Read your files",
|
|
1933
|
+
"filesWrite": "Upload and modify your files",
|
|
1934
|
+
"filesDelete": "Delete your files",
|
|
1935
|
+
"webhooksReceive": "Receive webhooks",
|
|
1936
|
+
"chatCompletions": "Use AI chat on your behalf",
|
|
1937
|
+
"modelsRead": "List available AI models",
|
|
1938
|
+
"federationWrite": "Act across federated services"
|
|
1939
|
+
},
|
|
1940
|
+
"account": {
|
|
1941
|
+
"title": "Signing in as"
|
|
1942
|
+
},
|
|
1943
|
+
"links": {
|
|
1944
|
+
"website": "Website",
|
|
1945
|
+
"privacy": "Privacy Policy",
|
|
1946
|
+
"terms": "Terms of Service"
|
|
1947
|
+
},
|
|
1948
|
+
"allow": "Continue to {{app}}",
|
|
1949
|
+
"deny": "Cancel",
|
|
1950
|
+
"disclaimer": "By continuing, {{app}} will be able to sign in with your Oxy account. You can manage connected apps anytime in your Oxy account settings."
|
|
1908
1951
|
}
|
|
1909
|
-
}
|
|
1952
|
+
}
|
|
@@ -581,7 +581,11 @@
|
|
|
581
581
|
"subtitle": "Añade otra cuenta para cambiar rápidamente entre ellas"
|
|
582
582
|
},
|
|
583
583
|
"label": "Selector de cuentas",
|
|
584
|
-
"searchPlaceholder": "Buscar cuentas"
|
|
584
|
+
"searchPlaceholder": "Buscar cuentas",
|
|
585
|
+
"scanTitle": "Escanea para iniciar sesión",
|
|
586
|
+
"scanSubtitle": "Abre la app de Oxy en tu móvil y escanea este código",
|
|
587
|
+
"scanWithOxy": "Escanea con la app de Oxy para continuar",
|
|
588
|
+
"scanQr": "Escanear código QR"
|
|
585
589
|
},
|
|
586
590
|
"feedback": {
|
|
587
591
|
"type": {
|
|
@@ -1905,5 +1909,44 @@
|
|
|
1905
1909
|
"earlyAdopter": "Pionero",
|
|
1906
1910
|
"earlyAdopterDesc": "Has formado parte de la comunidad desde el principio"
|
|
1907
1911
|
}
|
|
1912
|
+
},
|
|
1913
|
+
"consent": {
|
|
1914
|
+
"title": "Continuar a {{app}}",
|
|
1915
|
+
"subtitle": "Usa tu cuenta de Oxy para iniciar sesión en {{app}}. Revisa qué implica esta conexión antes de continuar.",
|
|
1916
|
+
"provenance": {
|
|
1917
|
+
"title": "Quién solicita acceso",
|
|
1918
|
+
"official": "Aplicación oficial de Oxy",
|
|
1919
|
+
"developer": "Publicada por {{developer}}",
|
|
1920
|
+
"thirdParty": "Aplicación de terceros"
|
|
1921
|
+
},
|
|
1922
|
+
"permissions": {
|
|
1923
|
+
"title": "Permisos solicitados",
|
|
1924
|
+
"basic": "Iniciar sesión y leer tu perfil básico"
|
|
1925
|
+
},
|
|
1926
|
+
"scopes": {
|
|
1927
|
+
"openid": "Confirmar tu identidad",
|
|
1928
|
+
"profile": "Leer tu perfil básico",
|
|
1929
|
+
"email": "Leer tu dirección de correo",
|
|
1930
|
+
"offlineAccess": "Mantener tu sesión iniciada cuando no estés",
|
|
1931
|
+
"userRead": "Leer tu perfil básico",
|
|
1932
|
+
"filesRead": "Leer tus archivos",
|
|
1933
|
+
"filesWrite": "Subir y modificar tus archivos",
|
|
1934
|
+
"filesDelete": "Eliminar tus archivos",
|
|
1935
|
+
"webhooksReceive": "Recibir webhooks",
|
|
1936
|
+
"chatCompletions": "Usar el chat con IA en tu nombre",
|
|
1937
|
+
"modelsRead": "Ver los modelos de IA disponibles",
|
|
1938
|
+
"federationWrite": "Actuar en servicios federados"
|
|
1939
|
+
},
|
|
1940
|
+
"account": {
|
|
1941
|
+
"title": "Iniciando sesión como"
|
|
1942
|
+
},
|
|
1943
|
+
"links": {
|
|
1944
|
+
"website": "Sitio web",
|
|
1945
|
+
"privacy": "Política de privacidad",
|
|
1946
|
+
"terms": "Términos del servicio"
|
|
1947
|
+
},
|
|
1948
|
+
"allow": "Continuar a {{app}}",
|
|
1949
|
+
"deny": "Cancelar",
|
|
1950
|
+
"disclaimer": "Al continuar, {{app}} podrá iniciar sesión con tu cuenta de Oxy. Puedes gestionar las apps conectadas cuando quieras en los ajustes de tu cuenta de Oxy."
|
|
1908
1951
|
}
|
|
1909
|
-
}
|
|
1952
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -304,9 +304,9 @@ export type {
|
|
|
304
304
|
SecurityActivity,
|
|
305
305
|
SecurityActivityResponse,
|
|
306
306
|
AssetUploadProgress,
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
307
|
+
DeviceLinkedSession,
|
|
308
|
+
DeviceLinkedSessionsResponse,
|
|
309
|
+
DeviceLinkedSessionLogoutResponse,
|
|
310
310
|
UpdateDeviceNameResponse,
|
|
311
311
|
} from './models/interfaces';
|
|
312
312
|
export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
|
|
@@ -494,20 +494,13 @@ export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
|
|
|
494
494
|
// ---------------------------------------------------------------------------
|
|
495
495
|
// Registrable-domain + central-IdP-apex helpers.
|
|
496
496
|
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
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.
|
|
497
|
+
// `registrableApex` (eTLD+1) is consumed via the `@oxyhq/core/server`
|
|
498
|
+
// re-export by `packages/api/src/utils/sameSite.ts` for same-site origin
|
|
499
|
+
// checks; `CENTRAL_IDP_APEX` by `server/cors.ts`'s `createOxyCors` (auto-allows
|
|
500
|
+
// `*.oxy.so`).
|
|
507
501
|
// ---------------------------------------------------------------------------
|
|
508
502
|
export { registrableApex } from './utils/registrableApex';
|
|
509
503
|
export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
|
|
510
|
-
export { SSO_CALLBACK_PATH } from './utils/ssoBounce';
|
|
511
504
|
|
|
512
505
|
export { runColdBoot } from './utils/coldBoot';
|
|
513
506
|
export type {
|
|
@@ -519,6 +512,20 @@ export type {
|
|
|
519
512
|
RunColdBootOptions,
|
|
520
513
|
} from './utils/coldBoot';
|
|
521
514
|
|
|
515
|
+
// ---------------------------------------------------------------------------
|
|
516
|
+
// OAuth 2.0 Authorization Code + PKCE helpers ("Sign in with Oxy" third party).
|
|
517
|
+
// Standard OAuth against auth.oxy.so/authorize — no FedCM/cookies/SSO bounce.
|
|
518
|
+
// ---------------------------------------------------------------------------
|
|
519
|
+
export {
|
|
520
|
+
buildOAuthAuthorizeUrl,
|
|
521
|
+
computeCodeChallenge,
|
|
522
|
+
generateOAuthState,
|
|
523
|
+
generatePkcePair,
|
|
524
|
+
DEFAULT_OAUTH_SCOPE,
|
|
525
|
+
OXY_AUTHORIZE_URL,
|
|
526
|
+
} from './utils/oauthPkce';
|
|
527
|
+
export type { PkcePair, BuildOAuthAuthorizeUrlParams } from './utils/oauthPkce';
|
|
528
|
+
|
|
522
529
|
// ---------------------------------------------------------------------------
|
|
523
530
|
// Session sync (device-scoped multi-account session client)
|
|
524
531
|
// ---------------------------------------------------------------------------
|
|
@@ -531,7 +538,7 @@ export type { SocketIOFactory, MinimalSocket } from './session/socketLoader';
|
|
|
531
538
|
|
|
532
539
|
// Shared SessionClient integration layer: the host adapter, the pure
|
|
533
540
|
// DeviceSessionState projection helpers, and the client factory are defined
|
|
534
|
-
// ONCE here so `@oxyhq/services`
|
|
541
|
+
// ONCE here so every `@oxyhq/services` platform variant reuses them instead of
|
|
535
542
|
// duplicating a local copy. Each consumer supplies its own `TokenTransport`
|
|
536
543
|
// (native vs. web mint strategies differ) to `createSessionClient`.
|
|
537
544
|
export { createSessionClientHost } from './session/sessionClientHost';
|
|
@@ -546,7 +553,7 @@ export {
|
|
|
546
553
|
// Unified account-list projection (THE single source of truth for the account
|
|
547
554
|
// chooser: device sign-ins ∪ account graph, deduped by accountId). Pure +
|
|
548
555
|
// I/O-free — the caller hydrates profiles via `getUsersByIds`. Shared by
|
|
549
|
-
// `@oxyhq/services
|
|
556
|
+
// `@oxyhq/services` and auth.oxy.so so the list can't diverge.
|
|
550
557
|
export {
|
|
551
558
|
projectSwitchableAccounts,
|
|
552
559
|
switchableAccountIds,
|
|
@@ -284,6 +284,10 @@ export interface Application {
|
|
|
284
284
|
name: string;
|
|
285
285
|
description?: string;
|
|
286
286
|
websiteUrl?: string;
|
|
287
|
+
/** Public privacy-policy URL, rendered as a legal link on the OAuth consent screen. */
|
|
288
|
+
privacyPolicyUrl?: string;
|
|
289
|
+
/** Public terms-of-service URL, rendered as a legal link on the OAuth consent screen. */
|
|
290
|
+
termsUrl?: string;
|
|
287
291
|
icon?: string;
|
|
288
292
|
type: ApplicationType;
|
|
289
293
|
status: ApplicationStatus;
|
|
@@ -342,6 +346,10 @@ export interface CreateApplicationInput {
|
|
|
342
346
|
name: string;
|
|
343
347
|
description?: string;
|
|
344
348
|
websiteUrl?: string;
|
|
349
|
+
/** Public privacy-policy URL (absolute `https://`). Shown on the OAuth consent screen. */
|
|
350
|
+
privacyPolicyUrl?: string;
|
|
351
|
+
/** Public terms-of-service URL (absolute `https://`). Shown on the OAuth consent screen. */
|
|
352
|
+
termsUrl?: string;
|
|
345
353
|
icon?: string;
|
|
346
354
|
redirectUris?: string[];
|
|
347
355
|
scopes?: string[];
|
|
@@ -357,6 +365,10 @@ export interface UpdateApplicationInput {
|
|
|
357
365
|
name?: string;
|
|
358
366
|
description?: string;
|
|
359
367
|
websiteUrl?: string;
|
|
368
|
+
/** Public privacy-policy URL (absolute `https://`, or `''` to clear). Shown on the OAuth consent screen. */
|
|
369
|
+
privacyPolicyUrl?: string;
|
|
370
|
+
/** Public terms-of-service URL (absolute `https://`, or `''` to clear). Shown on the OAuth consent screen. */
|
|
371
|
+
termsUrl?: string;
|
|
360
372
|
icon?: string;
|
|
361
373
|
redirectUris?: string[];
|
|
362
374
|
scopes?: string[];
|